using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Serialization;

namespace Rage
{
    [GenerateSerializer]
    public class Character
    {
        #region Constants
        /// <summary>The number of actual slots where items could go</summary>
        public const int SlotCount = 14;
        /// <summary>
        /// The number of slots where items could go, but that we would
        /// actually optimize against
        /// </summary>
        public const int OptimizableSlotCount = 14;
        private static readonly List<int> zeroSuffixList = new List<int>(new int[] { 0 });
        private static ItemSlot[] _itemSlots;
        public static ItemSlot[] ItemSlots
        {
            get
            {
                if (_itemSlots == null)
                {
#if SILVERLIGHT
                    _itemSlots = EnumHelper.GetValues<ItemSlot>();
#else
                    _itemSlots = (ItemSlot[])Enum.GetValues(typeof(ItemSlot));
#endif
                }
                return _itemSlots;
            }
        }
        private static CharacterSlot[] _characterSlots;
        public static CharacterSlot[] CharacterSlots
        {
            get
            {
                if (_characterSlots == null)
                {
#if SILVERLIGHT
                    _characterSlots = EnumHelper.GetValues<CharacterSlot>();
#else
                    _characterSlots = (CharacterSlot[])Enum.GetValues(typeof(CharacterSlot));
#endif
                }
                return _characterSlots;
            }
        }
        public static CharacterSlot[] EquippableCharacterSlots = {
            CharacterSlot.Head,
            CharacterSlot.Chest,
            CharacterSlot.Hands,
            CharacterSlot.Waist,
            CharacterSlot.Legs,
            CharacterSlot.Feet,

            CharacterSlot.Ear,
            CharacterSlot.Implant1,
            CharacterSlot.Implant2,
            CharacterSlot.Wrist,
            CharacterSlot.Relic1,
            CharacterSlot.Relic2,

            CharacterSlot.MainHand,
            CharacterSlot.OffHand,
        };
        #endregion

        #region Character Information: Basics (Name, Realm, Race, etc)
        #region Name
        /// <summary>Character Name: Bob</summary>
        [DefaultValue("")]
        public string Name { get { return _name; } set { _name = value; } }
        private string _name;
        #endregion
        #region Realm
        /// <summary>Realm: Stormrage, etc.</summary>
        [DefaultValue("")]
        public string Realm { get { return _realm; } set { _realm = value; } }
        private string _realm;
        #endregion
        #region Region
        /// <summary>Region: US, EU, KR, TW, or CN</summary>
        //[DefaultValue(CharacterRegion.US)]
        public CharacterRegion Region { get { return _region; } set { _region = value; } }
        private CharacterRegion _region = CharacterRegion.US;
        /// <summary>This is a Helper variable for use with the Stats Pane UI</summary>
        [XmlIgnore]
        public int RegionIndex { get { return (int)Region; } set { Region = (CharacterRegion)value; } }
        #endregion
        #region Race
        /// <summary>Race, such as Night Elf</summary>
        [XmlElement("Race")]//[DefaultValue(CharacterRace.NightElf)]
        public CharacterRace _race = CharacterRace.Human;
        [XmlIgnore]
        public CharacterRace Race { get { return _race; }
            set {
                if (_race != value) {
                    _race = value;
                    SetFaction();
                    OnRaceChanged();
                    OnCalculationsInvalidated();
                }
            }
        }
        /// <summary>This is a Helper variable for use with the Stats Pane UI</summary>
        [XmlIgnore]
        public int RaceIndex { get { return (int)Race; } set { Race = (CharacterRace)value; } }
        /// <summary>An event to attach to, allows other reactions in the program to a Race change</summary>
        public static event EventHandler RaceChanged;
        protected static void OnRaceChanged() { if (RaceChanged != null) RaceChanged(null, EventArgs.Empty); }
        #endregion
        #region Faction (Automatically determined by Race)
        /// <summary>Faction: Alliance, Horde</summary>
        [XmlElement("Faction")][DefaultValue(CharacterFaction.Republic)]
        private CharacterFaction _faction = CharacterFaction.Republic;
        [XmlIgnore]
        public CharacterFaction Faction { get { return _faction; } }
        private void SetFaction()
        {
            /*if (_race == CharacterRace.Draenei || _race == CharacterRace.Dwarf || _race == CharacterRace.Gnome || _race == CharacterRace.Human || _race == CharacterRace.NightElf || _race == CharacterRace.Worgen)
                _faction = CharacterFaction.Republic;
            else
            {*/
                _faction = CharacterFaction.Imperial;
            //}
        }
        #endregion
        #region Class
        /// <summary>Class: Druid, Warrior, etc.</summary>
        //[DefaultValue(CharacterClass.Druid)]
        public CharacterClass Class { get { return _class; } set { _class = value; OnClassChanged(); } }
        private CharacterClass _class = CharacterClass.Juggernaut;
        /// <summary>This is a Helper variable for use with the Stats Pane UI</summary>
        [XmlIgnore]
        public int ClassIndex { get { return (int)Class; } set { Class = (CharacterClass)value; } }
        public event EventHandler ClassChanged;
        public void OnClassChanged() { if (ClassChanged != null) { ClassChanged(this, EventArgs.Empty); } }
        #endregion
        #region Model
        /// <summary>The Current Model for this Character: Marauder vs Juggernaut, etc.</summary>
        [XmlElement("CurrentModel")] // this should not have a default value, otherwise loading a bear character while in cat will load it as cat
        public string _currentModel;
        [XmlIgnore]
        public string CurrentModel {
            get {
                if (string.IsNullOrEmpty(_currentModel)) {
                    foreach (KeyValuePair<string, Type> kvp in Calculations.Models) {
                        if (kvp.Value == Calculations.Instance.GetType()) {
                            _currentModel = kvp.Key;
                            Class = Calculations.ModelClasses[_currentModel];
                        }
                    }
                }
                return _currentModel;
            }
            set { _currentModel = value; }
        }
        #endregion
        #region Character Level (Always 50)
        /// <summary>The level of the Character. In Cataclysm, we support level 85.<br />
        /// Note that some models (Mage, Warlock) allow for alternate Character levels.
        /// They implement their own level set function rather than utilizing this variable.</summary>
        public int Level { get { return 50; } }
        #endregion
        #region Professions
        /*/// <summary>The Character's Primary Profession</summary>
        [DefaultValue(Profession.None)]
        public Profession PrimaryProfession { 
            get { return _primaryProfession; }
            set
            {
                if (_primaryProfession != value)
                {
                    _primaryProfession = value;
                    // this can be called while loading the xml, at that point the calculations is not set yet
                    // also we're not exactly changing value, just restoring to stored values
                    if (!IsLoading)
                    {
                        Calculations.UpdateProfessions(this);
                        ValidateActiveBuffs();
                    }
                }
            }
        }
        private Profession _primaryProfession = Profession.None;
        /// <summary>This is a Helper variable for use with the Stats Pane UI</summary>
        [XmlIgnore]
        /// <summary>The Character's Secondary Profession (NOT Cooking, First Aid, Archeology or Fishing)</summary>
        public int PriProfIndex
        {
            get { return Profs.ProfessionToIndex(PrimaryProfession); }
            set { PrimaryProfession = Profs.IndexToProfession(value); if (ProfessionChanged != null) { ProfessionChanged(this, new EventArgs()); } }
        }
        [DefaultValue(Profession.None)]
        public Profession SecondaryProfession
        {
            get { return _secondaryProfession; }
            set
            {
                if (_secondaryProfession != value)
                {
                    _secondaryProfession = value;
                    if (!IsLoading)
                    {
                        Calculations.UpdateProfessions(this);
                        ValidateActiveBuffs();
                    }
                }
            }
        }
        private Profession _secondaryProfession = Profession.None;
        /// <summary>This is a Helper variable for use with the Stats Pane UI</summary>
        [XmlIgnore]
        public int SecProfIndex
        {
            get { return Profs.ProfessionToIndex(SecondaryProfession); }
            set { SecondaryProfession = Profs.IndexToProfession(value); if (ProfessionChanged != null) { ProfessionChanged(this, new EventArgs()); } }
        }
        /// <summary>An event to attach to, allows other reactions in the program to a Professions' change</summary>
        public event EventHandler ProfessionChanged;
        /// <summary>
        /// Convenience function, checks both PrimaryProfession and SecondaryProfession for a match to provided profession check
        /// </summary>
        /// <param name="p">The Profession to match</param>
        /// <returns>True if one of the two professions matches, false if no match on either</returns>
        public bool HasProfession(Profession p)
        {
            if (PrimaryProfession == p) { return true; }
            if (SecondaryProfession == p) { return true; }
            return false;
        }
        /// <summary>
        /// Convenience function, checks both PrimaryProfession and SecondaryProfession for a match to provided professions check
        /// </summary>
        /// <param name="list">The Professions to match</param>
        /// <returns>True if one of the two professions matches anything in the list, false if no match on either</returns>
        public bool HasProfession(List<Profession> list)
        {
            foreach (Profession p in list)
            {
                if (HasProfession(p))
                    return true;
            }
            return false;
        }*/
        #endregion
        #region Light/Dark
        [DefaultValue(0)]
        public int LightDark { get { return _lightDark; } set { if (_lightDark != value) { _lightDark = value; OnCalculationsInvalidated(); } } }
        private int _lightDark = 0;
        #endregion
        #endregion
        #region Character Information: Intermediates (Buffs, Talents, Armory Pets, BS Sockets)
        #region Buffs
        /// <summary>Buffs: This variable stores the actual active buffs list in their Buff Class form.</summary>
        [XmlIgnore]
        public List<Buff> _activeBuffs;
        /// <summary>Buffs: This variable stores the active buffs list in their string names form,
        /// this allows it to store to the character xml file correctly.</summary>
        [XmlElement("ActiveBuffs")]
        public List<string> _activeBuffsXml;
        [XmlIgnore]
        public List<Buff> ActiveBuffs { get { return _activeBuffs; } set { _activeBuffs = value; ValidateActiveBuffs(); } }
        public void ActiveBuffsAdd(Buff buff) { if (buff != null) { ActiveBuffs.Add(buff); } }
        public void ActiveBuffsAdd(string buffName) {
            Buff buff = Buff.GetBuffByName(buffName);
            if (buff != null && !ActiveBuffs.Contains(buff)) {
                ActiveBuffs.Add(buff);
            }
        }
        public bool ActiveBuffsContains(string buff) {
            if (_activeBuffs == null) { return false; }
            return _activeBuffs.FindIndex(x => x.Name == buff) >= 0;
        }
        public bool ActiveBuffsConflictingBuffContains(string conflictingBuff) {
            return _activeBuffs.FindIndex(x => x.ConflictingBuffs.Contains(conflictingBuff)) >= 0;
        }
        public bool ActiveBuffsConflictingBuffContains(string name, string conflictingBuff) {
            return _activeBuffs.FindIndex(x => (x.ConflictingBuffs.Contains(conflictingBuff) && x.Name != name)) >= 0;
        }
        /// <summary>
        /// This function forces any duplicate buffs off the current buff list
        /// and enforces buffs that should be in there due to race/profession
        /// </summary>
        public void ValidateActiveBuffs() {
            // First let's check for Duplicate Buffs and remove them
            Buff cur = null;
            for (int i = 0; i < ActiveBuffs.Count;/*no default iter*/)
            {
                cur = ActiveBuffs[i];
                if (cur == null) { ActiveBuffs.RemoveAt(i); continue; } // don't iterate
                int count = 0;
                foreach (Buff iter in ActiveBuffs) 
                {
                    if (iter != null && iter.Name == cur.Name) count++;
                }
                if (count > 1) { ActiveBuffs.RemoveAt(i); continue; } // remove this first one, we'll check the other one(s) again later, don't iterate
                // At this point, we didn't fail so we can move on to the next one
                i++;
            }

            // Second let's check for Conflicting Buffs and remove them
            cur = null;
            for (int i = 0; i < ActiveBuffs.Count;/*no default iter*/)
            {
                cur = ActiveBuffs[i];
                if (cur == null) { ActiveBuffs.RemoveAt(i); continue; } // don't iterate
                int count = 0;
                foreach (Buff iter in ActiveBuffs) {
                    if (iter.Name == cur.Name) { continue; } // its the same buff, we dont need to compare against it
                    foreach (string conf in iter.ConflictingBuffs)
                    {
                        if (!string.IsNullOrEmpty(conf) && cur.ConflictingBuffs.Contains(conf))
                        {
                            count++;
                        }
                    }
                }
                if (count > 0) { ActiveBuffs.RemoveAt(i); continue; } // remove this first one, we'll check the other one(s) again later, don't iterate
                // At this point, we didn't fail so we can move on to the next one
                i++;
            }

            // Force a recalc, this will also update the Buffs tab since it's designed to react to that
            OnCalculationsInvalidated();
        }
        #endregion
        #region Talents
        [DefaultValue("")]
        [XmlElement("JuggernautTalents")]
        public string SerializableJuggernautTalents {
            get { return (string.IsNullOrWhiteSpace(JuggernautTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : JuggernautTalents.ToString(); }
            set { JuggernautTalents = new JuggernautTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("MarauderTalents")]
        public string SerializableMarauderTalents
        {
            get { return (string.IsNullOrWhiteSpace(MarauderTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : MarauderTalents.ToString(); }
            set { MarauderTalents = new MarauderTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("AssassinTalents")]
        public string SerializableAssassinTalents
        {
            get { return (string.IsNullOrWhiteSpace(AssassinTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : AssassinTalents.ToString(); }
            set { AssassinTalents = new AssassinTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("SorcererTalents")]
        public string SerializableSorcererTalents
        {
            get { return (string.IsNullOrWhiteSpace(SorcererTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : SorcererTalents.ToString(); }
            set { SorcererTalents = new SorcererTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("PowertechTalents")]
        public string SerializablePowertechTalents
        {
            get { return (string.IsNullOrWhiteSpace(PowertechTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : PowertechTalents.ToString(); }
            set { PowertechTalents = new PowertechTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("MercenaryTalents")]
        public string SerializableMercenaryTalents
        {
            get { return (string.IsNullOrWhiteSpace(MercenaryTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : MercenaryTalents.ToString(); }
            set { MercenaryTalents = new MercenaryTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("OperativeTalents")]
        public string SerializableOperativeTalents
        {
            get { return (string.IsNullOrWhiteSpace(OperativeTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : OperativeTalents.ToString(); }
            set { OperativeTalents = new OperativeTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("SniperTalents")]
        public string SerializableSniperTalents
        {
            get { return (string.IsNullOrWhiteSpace(SniperTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : SniperTalents.ToString(); }
            set { SniperTalents = new SniperTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("GuardianTalents")]
        public string SerializableGuardianTalents
        {
            get { return (string.IsNullOrWhiteSpace(GuardianTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : GuardianTalents.ToString(); }
            set { GuardianTalents = new GuardianTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("SentinelTalents")]
        public string SerializableSentinelTalents
        {
            get { return (string.IsNullOrWhiteSpace(SentinelTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : SentinelTalents.ToString(); }
            set { SentinelTalents = new SentinelTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("ShadowTalents")]
        public string SerializableShadowTalents
        {
            get { return (string.IsNullOrWhiteSpace(ShadowTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : ShadowTalents.ToString(); }
            set { ShadowTalents = new ShadowTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("SageTalents")]
        public string SerializableSageTalents
        {
            get { return (string.IsNullOrWhiteSpace(SageTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : SageTalents.ToString(); }
            set { SageTalents = new SageTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("VanguardTalents")]
        public string SerializableVanguardTalents
        {
            get { return (string.IsNullOrWhiteSpace(VanguardTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : VanguardTalents.ToString(); }
            set { VanguardTalents = new VanguardTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("CommandoTalents")]
        public string SerializableCommandoTalents
        {
            get { return (string.IsNullOrWhiteSpace(CommandoTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : CommandoTalents.ToString(); }
            set { CommandoTalents = new CommandoTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("ScoundrelTalents")]
        public string SerializableScoundrelTalents
        {
            get { return (string.IsNullOrWhiteSpace(ScoundrelTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : ScoundrelTalents.ToString(); }
            set { ScoundrelTalents = new ScoundrelTalents(value); }
        }
        [DefaultValue("")]
        [XmlElement("GunslingerTalents")]
        public string SerializableGunslingerTalents
        {
            get { return (string.IsNullOrWhiteSpace(GunslingerTalents.ToString().Replace("0", "").Replace(".", ""))) ? "" : GunslingerTalents.ToString(); }
            set { GunslingerTalents = new GunslingerTalents(value); }
        }

        [XmlIgnore]
        private JuggernautTalents _JuggernautTalents = null;
        [XmlIgnore]
        private MarauderTalents _MarauderTalents = null;
        [XmlIgnore]
        private AssassinTalents _AssassinTalents = null;
        [XmlIgnore]
        private SorcererTalents _SorcererTalents = null;
        [XmlIgnore]
        private PowertechTalents _PowertechTalents = null;
        [XmlIgnore]
        private MercenaryTalents _MercenaryTalents = null;
        [XmlIgnore]
        private OperativeTalents _OperativeTalents = null;
        [XmlIgnore]
        private SniperTalents _SniperTalents = null;
        [XmlIgnore]
        private GuardianTalents _GuardianTalents = null;
        [XmlIgnore]
        private SentinelTalents _SentinelTalents = null;
        [XmlIgnore]
        private ShadowTalents _ShadowTalents = null;
        [XmlIgnore]
        private SageTalents _SageTalents = null;
        [XmlIgnore]
        private VanguardTalents _VanguardTalents = null;
        [XmlIgnore]
        private CommandoTalents _CommandoTalents = null;
        [XmlIgnore]
        private ScoundrelTalents _ScoundrelTalents = null;
        [XmlIgnore]
        private GunslingerTalents _GunslingerTalents = null;

        [XmlIgnore]
        public JuggernautTalents JuggernautTalents { get { return _JuggernautTalents ?? (_JuggernautTalents = new JuggernautTalents()); } set { _JuggernautTalents = value; } }
        [XmlIgnore]
        public MarauderTalents MarauderTalents { get { return _MarauderTalents ?? (_MarauderTalents = new MarauderTalents()); } set { _MarauderTalents = value; } }
        [XmlIgnore]
        public AssassinTalents AssassinTalents { get { return _AssassinTalents ?? (_AssassinTalents = new AssassinTalents()); } set { _AssassinTalents = value; } }
        [XmlIgnore]
        public SorcererTalents SorcererTalents { get { return _SorcererTalents ?? (_SorcererTalents = new SorcererTalents()); } set { _SorcererTalents = value; } }
        [XmlIgnore]
        public PowertechTalents PowertechTalents { get { return _PowertechTalents ?? (_PowertechTalents = new PowertechTalents()); } set { _PowertechTalents = value; } }
        [XmlIgnore]
        public MercenaryTalents MercenaryTalents { get { return _MercenaryTalents ?? (_MercenaryTalents = new MercenaryTalents()); } set { _MercenaryTalents = value; } }
        [XmlIgnore]
        public OperativeTalents OperativeTalents { get { return _OperativeTalents ?? (_OperativeTalents = new OperativeTalents()); } set { _OperativeTalents = value; } }
        [XmlIgnore]
        public SniperTalents SniperTalents { get { return _SniperTalents ?? (_SniperTalents = new SniperTalents()); } set { _SniperTalents = value; } }

        [XmlIgnore]
        public GuardianTalents GuardianTalents { get { return _GuardianTalents ?? (_GuardianTalents = new GuardianTalents()); } set { _GuardianTalents = value; } }
        [XmlIgnore]
        public SentinelTalents SentinelTalents { get { return _SentinelTalents ?? (_SentinelTalents = new SentinelTalents()); } set { _SentinelTalents = value; } }
        [XmlIgnore]
        public ShadowTalents ShadowTalents { get { return _ShadowTalents ?? (_ShadowTalents = new ShadowTalents()); } set { _ShadowTalents = value; } }
        [XmlIgnore]
        public SageTalents SageTalents { get { return _SageTalents ?? (_SageTalents = new SageTalents()); } set { _SageTalents = value; } }
        [XmlIgnore]
        public VanguardTalents VanguardTalents { get { return _VanguardTalents ?? (_VanguardTalents = new VanguardTalents()); } set { _VanguardTalents = value; } }
        [XmlIgnore]
        public CommandoTalents CommandoTalents { get { return _CommandoTalents ?? (_CommandoTalents = new CommandoTalents()); } set { _CommandoTalents = value; } }
        [XmlIgnore]
        public ScoundrelTalents ScoundrelTalents { get { return _ScoundrelTalents ?? (_ScoundrelTalents = new ScoundrelTalents()); } set { _ScoundrelTalents = value; } }
        [XmlIgnore]
        public GunslingerTalents GunslingerTalents { get { return _GunslingerTalents ?? (_GunslingerTalents = new GunslingerTalents()); } set { _GunslingerTalents = value; } }

        [XmlIgnore]
        public TalentsBase CurrentTalents
        {
            get
            {
                switch (Class)
                {
                    // Imperial
                    case CharacterClass.Juggernaut: return JuggernautTalents;
                    case CharacterClass.Marauder: return MarauderTalents;
                    case CharacterClass.Assassin: return AssassinTalents;
                    case CharacterClass.Sorcerer: return SorcererTalents;
                    case CharacterClass.Powertech: return PowertechTalents;
                    case CharacterClass.Mercenary: return MercenaryTalents;
                    case CharacterClass.Operative: return OperativeTalents;
                    case CharacterClass.Sniper: return SniperTalents;
                    // Republic
                    case CharacterClass.Guardian: return GuardianTalents;
                    case CharacterClass.Sentinel: return SentinelTalents;
                    case CharacterClass.Shadow: return ShadowTalents;
                    case CharacterClass.Sage: return SageTalents;
                    case CharacterClass.Vanguard: return VanguardTalents;
                    case CharacterClass.Commando: return CommandoTalents;
                    case CharacterClass.Scoundrel: return ScoundrelTalents;
                    case CharacterClass.Gunslinger: return GunslingerTalents;
                    //
                    default: return JuggernautTalents;
                }
            }
            set
            {
                switch (Class)
                {
                    // Imperial
                    case CharacterClass.Juggernaut: JuggernautTalents = value as JuggernautTalents; break;
                    case CharacterClass.Marauder: MarauderTalents = value as MarauderTalents; break;
                    case CharacterClass.Assassin: AssassinTalents = value as AssassinTalents; break;
                    case CharacterClass.Sorcerer: SorcererTalents = value as SorcererTalents; break;
                    case CharacterClass.Powertech: PowertechTalents = value as PowertechTalents; break;
                    case CharacterClass.Mercenary: MercenaryTalents = value as MercenaryTalents; break;
                    case CharacterClass.Operative: OperativeTalents = value as OperativeTalents; break;
                    case CharacterClass.Sniper: SniperTalents = value as SniperTalents; break;
                    // Republic
                    case CharacterClass.Guardian: GuardianTalents = value as GuardianTalents; break;
                    case CharacterClass.Sentinel: SentinelTalents = value as SentinelTalents; break;
                    case CharacterClass.Shadow: ShadowTalents = value as ShadowTalents; break;
                    case CharacterClass.Sage: SageTalents = value as SageTalents; break;
                    case CharacterClass.Vanguard: VanguardTalents = value as VanguardTalents; break;
                    case CharacterClass.Commando: CommandoTalents = value as CommandoTalents; break;
                    case CharacterClass.Scoundrel: ScoundrelTalents = value as ScoundrelTalents; break;
                    case CharacterClass.Gunslinger: GunslingerTalents = value as GunslingerTalents; break;
                    //
                    default: JuggernautTalents = value as JuggernautTalents; break;
                }
            }
        }

        public event EventHandler TalentChangedEvent;
        public void OnTalentChange()
        {
            if (TalentChangedEvent != null)
                TalentChangedEvent(this, EventArgs.Empty);
        }
        #endregion
        #region Blacksmithing Sockets
        private bool headAugSocket = false;
        private bool chestAugSocket = false;
        private bool handsAugSocket = false;
        private bool waistAugSocket = false;
        private bool legsAugSocket = false;
        private bool feetAugSocket = false;
        private bool earAugSocket = false;
        private bool implant1AugSocket = false;
        private bool implant2AugSocket = false;
        private bool wristAugSocket = false;
        private bool relic1AugSocket = false;
        private bool relic2AugSocket = false;
        private bool mainHandAugSocket = false;
        private bool offHandAugSocket = false;
        [DefaultValue(false)]
        public bool HeadAugmentSocketEnabled { get { return headAugSocket; } set { headAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool ChestAugmentSocketEnabled { get { return chestAugSocket; } set { chestAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool HandsAugmentSocketEnabled { get { return handsAugSocket; } set { handsAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool WaistAugmentSocketEnabled { get { return waistAugSocket; } set { waistAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool LegsAugmentSocketEnabled { get { return legsAugSocket; } set { legsAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool FeetAugmentSocketEnabled { get { return feetAugSocket; } set { feetAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool EarAugmentSocketEnabled { get { return earAugSocket; } set { earAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool Implant1AugmentSocketEnabled { get { return implant1AugSocket; } set { implant1AugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool Implant2AugmentSocketEnabled { get { return implant2AugSocket; } set { implant2AugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool WristAugmentSocketEnabled { get { return wristAugSocket; } set { wristAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool Relic1AugmentSocketEnabled { get { return relic1AugSocket; } set { relic1AugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool Relic2AugmentSocketEnabled { get { return relic2AugSocket; } set { relic2AugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool MainHandAugmentSocketEnabled { get { return mainHandAugSocket; } set { mainHandAugSocket = value; OnCalculationsInvalidated(); } }
        [DefaultValue(false)]
        public bool OffHandAugmentSocketEnabled { get { return offHandAugSocket; } set { offHandAugSocket = value; OnCalculationsInvalidated(); } }
        #endregion
        #endregion
        #region Character Information: Equipped Item Slots
        #region As Gemmed ID's (How they are stored to the XML)
        /// <summary>
        /// A Function to convert the item from a specific slot into it's string form with gemming info.
        /// If the specified slot is empty this function will return an empty string.
        /// </summary>
        /// <param name="slot">The Slot to pull from.</param>
        /// <returns>ItemId.RandomSuffixId.Gem1.Gem2.Gem3.EnchantId.ReforgeId.TinkeringId
        /// <br />56278.0.52291.52219.0.4208.89.0</returns>
        private string GetGemmedId(CharacterSlot slot)
        {
            ItemInstance item = this[slot];
            if ((object)item == null) return null;
            return item.ItemModdedId;
        }
        /// <summary>
        /// A Function to take an item in it's Gemmed string form and set character's slot to that.
        /// </summary>
        /// <param name="slot">The Slot to set</param>
        /// <param name="gemmedId">The string gemmed id to put into the slot</param>
        private void SetGemmedId(CharacterSlot slot, string gemmedId)
        {
            if (string.IsNullOrEmpty(gemmedId)) _item[(int)slot] = null;
            else _item[(int)slot] = new ItemInstance(gemmedId); // don't call invalidations all the time while loading character
        }
        [XmlElement("Head")]
        public string _head { get { return GetGemmedId(CharacterSlot.Head); } set { SetGemmedId(CharacterSlot.Head, value); } }
        [XmlElement("Chest")]
        public string _chest { get { return GetGemmedId(CharacterSlot.Chest); } set { SetGemmedId(CharacterSlot.Chest, value); } }
        [XmlElement("Hands")]
        public string _hands { get { return GetGemmedId(CharacterSlot.Hands); } set { SetGemmedId(CharacterSlot.Hands, value); } }
        [XmlElement("Waist")]
        public string _waist { get { return GetGemmedId(CharacterSlot.Waist); } set { SetGemmedId(CharacterSlot.Waist, value); } }
        [XmlElement("Legs")]
        public string _legs { get { return GetGemmedId(CharacterSlot.Legs); } set { SetGemmedId(CharacterSlot.Legs, value); } }
        [XmlElement("Feet")]
        public string _feet { get { return GetGemmedId(CharacterSlot.Feet); } set { SetGemmedId(CharacterSlot.Feet, value); } }
        [XmlElement("Ear")]
        public string _ear { get { return GetGemmedId(CharacterSlot.Ear); } set { SetGemmedId(CharacterSlot.Ear, value); } }
        [XmlElement("Implant1")]
        public string _implant1 { get { return GetGemmedId(CharacterSlot.Implant1); } set { SetGemmedId(CharacterSlot.Implant1, value); } }
        [XmlElement("Implant2")]
        public string _implant2 { get { return GetGemmedId(CharacterSlot.Implant2); } set { SetGemmedId(CharacterSlot.Implant2, value); } }
        [XmlElement("Wrist")]
        public string _wrist { get { return GetGemmedId(CharacterSlot.Wrist); } set { SetGemmedId(CharacterSlot.Wrist, value); } }
        [XmlElement("Relic1")]
        public string _relic1 { get { return GetGemmedId(CharacterSlot.Relic1); } set { SetGemmedId(CharacterSlot.Relic1, value); } }
        [XmlElement("Relic2")]
        public string _relic2 { get { return GetGemmedId(CharacterSlot.Relic2); } set { SetGemmedId(CharacterSlot.Relic2, value); } }
        [XmlElement("MainHand")]
        public string _mainHand { get { return GetGemmedId(CharacterSlot.MainHand); } set { SetGemmedId(CharacterSlot.MainHand, value); } }
        [XmlElement("OffHand")]
        public string _offHand { get { return GetGemmedId(CharacterSlot.OffHand); } set { SetGemmedId(CharacterSlot.OffHand, value); } }
        #endregion
        #endregion
        #region Character Information: Saved Item Sets Lists for Comparing Sets
        [XmlElement("ItemSetList")]
        public List<string> _itemSetListXML = null;
        [XmlIgnore]
        private ItemSetList itemSetList = new ItemSetList() { /*new ItemSet() { Name = "Naked", }*/ };
        public ItemSetList GetItemSetList() { return itemSetList; }
        /// <summary>
        /// Adds an ItemSet to the ItemSetList for this Character.
        /// ItemSets are for the Comparison chart.
        /// It will automatically check to see if the ItemSet is already in the list.
        /// </summary>
        /// <param name="newset"></param>
        public void AddToItemSetList(ItemSet newset)
        {
            if (itemSetList == null) return;
            bool didsomething = false;
            if (!ItemSetListContainsItemSetByName(newset.Name)) {
                itemSetList.Add(newset);
                didsomething |= true;
            } else {
                // Remove the original and replace it
                RemoveFromItemSetList(newset);
                itemSetList.Add(newset);
                didsomething |= true;
            }
            if (didsomething) { OnCalculationsInvalidated(); }
        }
        /// <summary>
        /// Remove an ItemSet to the ItemSetList for this Character.
        /// ItemSets are for the Comparison chart.
        /// It will run the Remove command until it stops finding the set in the ItemSetList, in case its there multiple times
        /// </summary>
        /// <param name="newset"></param>
        public void RemoveFromItemSetList(ItemSet newset)
        {
            if (itemSetList == null) return;
            // Using a While in case it's been added multiple times by accident
            bool didsomething = false;
            while (ItemSetListContainsItemSetByName(newset.Name))
            {
                itemSetList.RemoveAll(set => (set.Name == newset.Name));
                didsomething |= true;
            }
            if (didsomething) { OnCalculationsInvalidated(); }
        }
        public void RemoveFromItemSetListByName(String name) {
            if (itemSetList == null || itemSetList.Count <= 0) { return; }
            bool didsomething = false;
            for (int i = 0; i < itemSetList.Count; ) {
                if (itemSetList[i].Name == name) {
                    itemSetList.RemoveAt(i);
                    didsomething |= true;
                } else { i++; }
            }
            if (didsomething) { OnCalculationsInvalidated(); }
        }
        public void ClearItemSetList() {
            if (itemSetList == null) return;
            itemSetList.Clear();
            //itemSetList.Add(new ItemSet() { Name = "Naked", });
            OnCalculationsInvalidated();
        }
        public bool ItemSetListContainsItemSet(ItemSet IS)
        {
            if (itemSetList == null || itemSetList.Count <= 0) return false;
            bool contains = false;
            foreach (ItemSet ISs in itemSetList)
            {
                if (IS.Equals(ISs)) {
                    contains = true;
                    break;
                }
            }
            return contains;
            //return itemSetList.Contains(IS);
        }
        public bool ItemSetListContainsItemSetByName(String IS)
        {
            if (itemSetList == null || itemSetList.Count <= 0) return false;
            bool contains = false;
            foreach (ItemSet ISs in itemSetList)
            {
                if (ISs.Name.Equals(IS))
                {
                    contains = true;
                    break;
                }
            }
            return contains;
            //return itemSetList.Contains(IS);
        }
        public void EquipItemSetByName(String name)
        {
            if (itemSetList == null || itemSetList.Count <= 0) { return; }
            foreach (ItemSet IS in itemSetList) {
                if (name == IS.Name) {
                    foreach (CharacterSlot cs in Character.EquippableCharacterSlots) {
                        this[cs] = IS[cs];
                    }
                    break;
                }
            }
        }
        public int GetNumItemSetsFromOptimizer() {
            if (itemSetList == null || itemSetList.Count <= 0) { return 0; }
            int count = 0;
            foreach (ItemSet IS in itemSetList) {
                // potential fix for http://rage.codeplex.com/workitem/21371
                if (IS != null && IS.Name != null & IS.Name.Contains("Optimized GearSet")) { count++; }
            }
            return count;
        }
        /// <summary>Warning! Retuns NULL when it can't find the set</summary>
        public ItemSet GetItemSetByName(String name) {
            if (name == "Current") {
                ItemSet current = new ItemSet();
                foreach (CharacterSlot cs in Character.EquippableCharacterSlots) {
                    current.Add(this[cs]);
                }
                current.Name = "Current";
                return current;
            }
            if (itemSetList == null || itemSetList.Count <= 0) { return null; }
            if (ItemSetListContainsItemSetByName(name)) {
                foreach (ItemSet ISs in itemSetList) {
                    if (ISs.Name.Equals(name)) { return ISs; }
                }
            }
            return null;
        }
        #endregion
        #region Character Information: Calculation Options Panes Settings (Stored as a serialized xml string rather than just XML)
        [XmlElement("CalculationOptions")]
        public SerializableDictionary<string, string> _serializedCalculationOptions;
        public void SerializeCalculationOptions()
        {
            if (CalculationOptions != null)
            {
                if (_serializedCalculationOptions == null)
                {
                    _serializedCalculationOptions = new SerializableDictionary<string, string>();
                }
                _serializedCalculationOptions[CurrentModel] = CalculationOptions.GetXml();
            }
        }
        private Dictionary<string, ICalculationOptionBase> _calculationOptions;
        [XmlIgnore]
        public ICalculationOptionBase CalculationOptions {
            get 
            {
                ICalculationOptionBase ret;
                if (_calculationOptions.TryGetValue(CurrentModel, out ret)) {
                    return ret;
                } else {
                    return LoadCalculationOptions();
                }
            }
            set { _calculationOptions[CurrentModel] = value; }
        }
        private ICalculationOptionBase LoadCalculationOptions()
        {
            if (_serializedCalculationOptions != null && _serializedCalculationOptions.ContainsKey(CurrentModel))
            {
                ICalculationOptionBase ret = Calculations.GetModel(CurrentModel)
                    .DeserializeDataObject(_serializedCalculationOptions[CurrentModel]);

                // set parent Character for models that need backward link
                ICharacterCalculationOptions characterCalculationOptions =
                    ret as ICharacterCalculationOptions;
                if (characterCalculationOptions != null)
                    characterCalculationOptions.Character = this;

                _calculationOptions[CurrentModel] = ret;
                return ret;
            }
            return null;
        }
        #endregion
        #region Character Information: Boss Options Pane Settings (Stored as XML)
        [XmlElement("Boss")]
        public BossOptions SerializableBoss {
            get { return BossOptions; }
            set { BossOptions = value.Clone(); }
        }
        [XmlIgnore]
        private BossOptions _bossOptions = null;
        [XmlIgnore]
        public BossOptions BossOptions
        {
            get {
                if (_bossOptions == null) {
                    _bossOptions = new BossOptions();
                    _bossOptions.Attacks.Add(BossOptions.ADefaultMeleeAttack);
                }
                return _bossOptions;
            }
            set { _bossOptions = value; }
        }
        #endregion
        #region Settings: Item Filters Enables/Disables
        #region Constants for Filters
        private struct RangeValue { public int Min; public int Max; }
        private struct PercRangeValue { public float Min; public float Max; }
        private readonly static RangeValue[] RangeValues = new RangeValue[] {
            new RangeValue { Min = 000, Max = 001 }, // 0
            new RangeValue { Min = 002, Max = 199 }, // 1
            new RangeValue { Min = 200, Max = 284 }, // 2
            new RangeValue { Min = 285, Max = 333 }, // 3
            new RangeValue { Min = 334, Max = 352 }, // 4
            new RangeValue { Min = 353, Max = 358 }, // 5
            new RangeValue { Min = 359, Max = 364 }, // 6
            new RangeValue { Min = 365, Max = 371 }, // 7
            new RangeValue { Min = 372, Max = 377 }, // 8
            new RangeValue { Min = 378, Max = 383 }, // 9
            new RangeValue { Min = 384, Max = 390 }, // 10
            new RangeValue { Min = 391, Max = 396 }, // 11
            new RangeValue { Min = 397, Max = 409 }, // 12
            new RangeValue { Min = 410, Max = 416 }, // 13
        };
        private readonly static PercRangeValue[] DropRangeValues = new PercRangeValue[] {
            new PercRangeValue { Min = 0.00f, Max = 0.01f },
            new PercRangeValue { Min = 0.01f, Max = 0.03f },
            new PercRangeValue { Min = 0.03f, Max = 0.05f },
            new PercRangeValue { Min = 0.05f, Max = 0.10f },
            new PercRangeValue { Min = 0.10f, Max = 0.15f },
            new PercRangeValue { Min = 0.15f, Max = 0.20f },
            new PercRangeValue { Min = 0.20f, Max = 0.25f },
            new PercRangeValue { Min = 0.25f, Max = 0.29f },
            new PercRangeValue { Min = 0.29f, Max = 0.39f },
            new PercRangeValue { Min = 0.39f, Max = 0.49f },
            new PercRangeValue { Min = 0.50f, Max = 1.001f },
        };
        #endregion

        /// <summary>This modified call allows it to speed up character loading</summary>
        private void OnFiltersChanged()
        {
            if (!IsLoading)
            {
                ItemCache.OnItemsChanged();
            }
        }

        #region ItemFilters by iLevel
        public bool ItemMatchesiLvlCheck(Item item)
        {
            if (item.Type == ItemType.None
                && (item.Slot == ItemSlot.Armoring || item.Slot == ItemSlot.Barrel || item.Slot == ItemSlot.Hilt
                || item.Slot == ItemSlot.Mod || item.Slot == ItemSlot.Enhancement || item.Slot == ItemSlot.ColorCrystal
                || item.Slot == ItemSlot.Augment))
            { return true; } // Don't filter gems
            //
            bool retVal = false;
            //
            if (iLvl_UseChecks) {
                // We only need 1 match to make it true
                for (int i = 0; i < _iLvl.Length; i++)
                {
                    if (iLvl[i] && (item.ItemLevel >= RangeValues[i].Min && item.ItemLevel <= RangeValues[i].Max))
                    {
                        retVal = true;
                        break;
                    }
                }
            } else {
                if (item.ItemLevel >= ilvlF_SLMin && item.ItemLevel <= ilvlF_SLMax) {
                    retVal = true;
                }
            }
            //
            return retVal;
        }

        [XmlIgnore]
        private bool _iLvl_UseChecks = true;
        [XmlElement("ItemFiltersSettings_UseChecks")][DefaultValue(true)]
        public bool iLvl_UseChecks { get { return _iLvl_UseChecks; } set { _iLvl_UseChecks = value; OnFiltersChanged(); } }
        [XmlIgnore]
        private bool[] _iLvl = new bool[] {
            true, // 0 000-001 (Heirloom)
            true, // 1 002-199 (Tier 01-06)
            true, // 2 200-284 (Tier 07-10)
            true, // 3 285-333 (Cata Dungeons)
            true, // 4 334-358 (Cata Heroics)
            true, // 5 353-358 (Rise of Zandalar)
            true, // 6 359-364 (Tier 11.0)
            true, // 7 365-371 (Molten Front)
            true, // 8 372 - 377 (Tier 11.5)
            true, // 9 378 - 383 (Tier 12.0)
            true, // 10 384 - 390 (Tier 13.LFR)
            true, // 11 391 - 396 (Tier 12.5)
            true, // 12 397 - 409 (Tier 13.0)
            true, // 13 410 - 416 (Tier 13.5)
        };
        [XmlIgnore]
        public bool[] iLvl {
            get {
                if (_iLvl == null) {
                    _iLvl = new bool[] {
                        true, // 0 000 - 001 (Heirloom)
                        true, // 1 002 - 199 (Tier 01-06)
                        true, // 2 200 - 284 (Tier 07-10)
                        true, // 3 285 - 333 (Cata Dungeons)
                        true, // 4 334 - 358 (Cata Heroics)
                        true, // 5 353-358 (Rise of Zandalar)
                        true, // 6 359-364 (Tier 11.0)
                        true, // 7 365-371 (Molten Front)
                        true, // 8 372 - 377 (Tier 11.5)
                        true, // 9 378 - 383 (Tier 12.0)
                        true, // 10 384 - 390 (Tier 13.LFR)
                        true, // 11 391 - 396 (Tier 12.5)
                        true, // 12 397 - 409 (Tier 13.0)
                        true, // 13 410 - 416 (Tier 13.5)
                    };
                }
                return _iLvl;
            }
            set {
                if (value == null) {
                    _iLvl = new bool[] {
                        true, // 0 000 - 001 (Heirloom)
                        true, // 1 002 - 199 (Tier 01-06)
                        true, // 2 200 - 284 (Tier 07-10)
                        true, // 3 285 - 333 (Cata Dungeons)
                        true, // 4 334 - 358 (Cata Heroics)
                        true, // 5 353-358 (Rise of Zandalar)
                        true, // 6 359-364 (Tier 11.0)
                        true, // 7 365-371 (Molten Front)
                        true, // 8 372 - 377 (Tier 11.5)
                        true, // 9 378 - 383 (Tier 12.0)
                        true, // 10 384 - 390 (Tier 13.LFR)
                        true, // 11 391 - 396 (Tier 12.5)
                        true, // 12 397 - 409 (Tier 13.0)
                        true, // 13 410 - 416 (Tier 13.5)
                    };
                } else {
                    _iLvl = value;
                }
                OnFiltersChanged();
            }
        }
        [XmlElement("ItemFiltersSettings_0")][DefaultValue(true)]
        public bool ilvlF_0 { get { return _iLvl[0]; } set { _iLvl[0] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_1")][DefaultValue(true)]
        public bool ilvlF_1 { get { return _iLvl[1]; } set { _iLvl[1] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_2")][DefaultValue(true)]
        public bool ilvlF_2 { get { return _iLvl[2]; } set { _iLvl[2] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_3")][DefaultValue(true)]
        public bool ilvlF_3 { get { return _iLvl[3]; } set { _iLvl[3] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_4")][DefaultValue(true)]
        public bool ilvlF_4 { get { return _iLvl[4]; } set { _iLvl[4] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_5")][DefaultValue(true)]
        public bool ilvlF_5 { get { return _iLvl[5]; } set { _iLvl[5] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_6")][DefaultValue(true)]
        public bool ilvlF_6 { get { return _iLvl[6]; } set { _iLvl[6] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_7")][DefaultValue(true)]
        public bool ilvlF_7 { get { return _iLvl[7]; } set { _iLvl[7] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_8")][DefaultValue(true)]
        public bool ilvlF_8 { get { return _iLvl[8]; } set { _iLvl[8] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_9")][DefaultValue(true)]
        public bool ilvlF_9 { get { return _iLvl[9]; } set { _iLvl[9] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_10")][DefaultValue(true)]
        public bool ilvlF_10 { get { return _iLvl[10]; } set { _iLvl[10] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_11")][DefaultValue(true)]
        public bool ilvlF_11 { get { return _iLvl[11]; } set { _iLvl[11] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_12")][DefaultValue(true)]
        public bool ilvlF_12 { get { return _iLvl[12]; } set { _iLvl[12] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersSettings_13")][DefaultValue(true)]
        public bool ilvlF_13 { get { return _iLvl[13]; } set { _iLvl[13] = value; OnFiltersChanged(); } }

        [XmlIgnore]
        private double _ilvlF_SLMin = 285;
        [XmlElement("ItemFiltersSettings_SLMin")][DefaultValue(285)]
        public double ilvlF_SLMin { get { return _ilvlF_SLMin; } set { _ilvlF_SLMin = value; OnFiltersChanged(); } }
        [XmlIgnore]
        private double _ilvlF_SLMax = 416;
        [XmlElement("ItemFiltersSettings_SLMax")][DefaultValue(416)]
        public double ilvlF_SLMax { get { return _ilvlF_SLMax; } set { _ilvlF_SLMax = value; OnFiltersChanged(); } }
        #endregion

        #region ItemFilters by Drop Rate
        public bool ItemMatchesDropCheck(Item item)
        {
            if (item.Type == ItemType.None
                && (item.Slot == ItemSlot.Armoring || item.Slot == ItemSlot.Barrel || item.Slot == ItemSlot.Hilt
                || item.Slot == ItemSlot.Mod || item.Slot == ItemSlot.Enhancement || item.Slot == ItemSlot.ColorCrystal
                || item.Slot == ItemSlot.Augment))
            { return true; } // Don't filter gems
            //
            bool retVal = false;
            // First, check to see if any of the sources is based on Drop
            int index = -1;
            int type = 0;
            for (int i = 0; i < item.LocationInfo.Count; )
            {
                /*if (item.LocationInfo[i] == null) { item.LocationInfo.RemoveAt(i); }
                else*/ if (item.LocationInfo[i].GetType() == typeof(StaticDrop))    { index = i; type = 1; break; }
                //else if (item.LocationInfo[i].GetType() == typeof(WorldDrop))     { index = i; type = 2; break; }
                //else if (item.LocationInfo[i].GetType() == typeof(ContainerItem)) { index = i; type = 3; break; }
                else { i++; }
            }
            if (index == -1) { return true; } // ignoring the concept of drop filtering because it's not tied to a drop
            //
            float dropPerc = (type == 1 ? (item.LocationInfo[index] as StaticDrop).DropPerc/*
                            : type == 2 ? (item.LocationInfo[index] as WorldDrop).DropPerc
                            : type == 3 ? (item.LocationInfo[index] as ContainerItem).DropPerc*/
                            : 0f);
            if (float.IsNaN(dropPerc))
            {
                return true; // bad data, can happen, don't filter out
            }
            //
            if (Drop_UseChecks) {
                // We only need 1 match to make it true
                for (int i = 0; i < _Drop.Length; i++) {
                    if (Drop[i] && (dropPerc >= DropRangeValues[i].Min && dropPerc < DropRangeValues[i].Max)) {
                        retVal = true;
                        break;
                    }
                }
            } else {
                if (dropPerc >= DropF_SLMin && dropPerc <= DropF_SLMax) {
                    retVal = true;
                }
            }
            //
            return retVal;
        }

        [XmlIgnore]
        private bool _Drop_UseChecks = true;
        [XmlElement("ItemFiltersDropSettings_UseChecks")][DefaultValue(true)]
        public bool Drop_UseChecks { get { return _Drop_UseChecks; } set { _Drop_UseChecks = value; OnFiltersChanged(); } }
        [XmlIgnore]
        private bool[] _Drop = new bool[] {
            true, //  0   1%
            true, //  1   3%
            true, //  2   5%
            true, //  3  10%
            true, //  4  15%
            true, //  5  20%
            true, //  6  25%
            true, //  7  29%
            true, //  8  39%
            true, //  9  49%
            true, // 10 100%
        };
        [XmlIgnore]
        public bool[] Drop
        {
            get
            {
                if (_Drop == null)
                {
                    _Drop = new bool[] {
                        true, //  0   1%
                        true, //  1   3%
                        true, //  2   5%
                        true, //  3  10%
                        true, //  4  15%
                        true, //  5  20%
                        true, //  6  25%
                        true, //  7  29%
                        true, //  8  39%
                        true, //  9  49%
                        true, // 10 100%
                    };
                }
                return _Drop;
            }
            set
            {
                if (value == null)
                {
                    _Drop = new bool[] {
                        true, //  0   1%
                        true, //  1   3%
                        true, //  2   5%
                        true, //  3  10%
                        true, //  4  15%
                        true, //  5  20%
                        true, //  6  25%
                        true, //  7  29%
                        true, //  8  39%
                        true, //  9  49%
                        true, // 10 100%
                    };
                }
                else
                {
                    _Drop = value;
                }
                OnFiltersChanged();
            }
        }
        [XmlElement("ItemFiltersDropSettings_01")][DefaultValue(true)]
        public bool DropF_00 { get { return _Drop[0]; } set { _Drop[0] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_03")][DefaultValue(true)]
        public bool DropF_01 { get { return _Drop[1]; } set { _Drop[1] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_05")][DefaultValue(true)]
        public bool DropF_02 { get { return _Drop[2]; } set { _Drop[2] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_10")][DefaultValue(true)]
        public bool DropF_03 { get { return _Drop[3]; } set { _Drop[3] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_15")][DefaultValue(true)]
        public bool DropF_04 { get { return _Drop[4]; } set { _Drop[4] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_20")][DefaultValue(true)]
        public bool DropF_05 { get { return _Drop[5]; } set { _Drop[5] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_25")][DefaultValue(true)]
        public bool DropF_06 { get { return _Drop[6]; } set { _Drop[6] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_29")][DefaultValue(true)]
        public bool DropF_07 { get { return _Drop[7]; } set { _Drop[7] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_39")][DefaultValue(true)]
        public bool DropF_08 { get { return _Drop[8]; } set { _Drop[8] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_49")][DefaultValue(true)]
        public bool DropF_09 { get { return _Drop[9]; } set { _Drop[9] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersDropSettings_100")][DefaultValue(true)]
        public bool DropF_10 { get { return _Drop[10]; } set { _Drop[10] = value; OnFiltersChanged(); } }

        [XmlIgnore]
        private double _DropF_SLMin = 0.00f;
        [XmlElement("ItemFiltersDropSettings_SLMin")][DefaultValue(0.00f)]
        public double DropF_SLMin { get { return _DropF_SLMin; } set { _DropF_SLMin = value; OnFiltersChanged(); } }
        [XmlIgnore]
        private double _DropF_SLMax = 1.001f;
        [XmlElement("ItemFiltersDropSettings_SLMax")][DefaultValue(1.001f)]
        public double DropF_SLMax { get { return _DropF_SLMax; } set { _DropF_SLMax = value; OnFiltersChanged(); } }
        #endregion

        #region ItemFilters by Bind Type
        public bool ItemMatchesBindCheck(Item item)
        {
            if (item.Type == ItemType.None
                && (item.Slot == ItemSlot.Armoring || item.Slot == ItemSlot.Hilt || item.Slot == ItemSlot.Barrel
                || item.Slot == ItemSlot.Mod || item.Slot == ItemSlot.Enhancement || item.Slot == ItemSlot.ColorCrystal
                || item.Slot == ItemSlot.Augment))
            { return true; } // Don't filter gems
            //
            bool retVal = false;
            //
            // We only need 1 match to make it true
            if (bindF_0 && item.Bind == BindsOn.None) { retVal = true; }
            if (bindF_1 && item.Bind == BindsOn.BoA ) { retVal = true; }
            if (bindF_2 && item.Bind == BindsOn.BoU ) { retVal = true; }
            if (bindF_3 && item.Bind == BindsOn.BoE ) { retVal = true; }
            if (bindF_4 && item.Bind == BindsOn.BoP ) { retVal = true; }
            //
            return retVal;
        }

        [XmlIgnore]
        private bool[] _bind = new bool[] {
            true, // 0 Doesn't Bind
            true, // 1 Binds To Account
            true, // 2 Binds on Use
            true, // 3 Binds on Equip
            true, // 4 Binds on Pickup
        };
        [XmlIgnore]
        public bool[] bind {
            get {
                if (_bind == null) {
                    _bind = new bool[] {
                        true, // 0 Doesn't Bind
                        true, // 1 Binds To Account
                        true, // 2 Binds on Use
                        true, // 3 Binds on Equip
                        true, // 4 Binds on Pickup
                    };
                }
                return _bind;
            }
            set {
                if (value == null) {
                    _bind = new bool[] {
                        true, // 0 Doesn't Bind
                        true, // 1 Binds To Account
                        true, // 2 Binds on Use
                        true, // 3 Binds on Equip
                        true, // 4 Binds on Pickup
                    };
                } else { _bind = value; }
                OnFiltersChanged();
            }
        }
        [XmlElement("ItemFiltersBindSettings_0")][DefaultValue(true)]
        public bool bindF_0 { get { return _bind[0]; } set { _bind[0] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersBindSettings_1")][DefaultValue(true)]
        public bool bindF_1 { get { return _bind[1]; } set { _bind[1] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersBindSettings_2")][DefaultValue(true)]
        public bool bindF_2 { get { return _bind[2]; } set { _bind[2] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersBindSettings_3")][DefaultValue(true)]
        public bool bindF_3 { get { return _bind[3]; } set { _bind[3] = value; OnFiltersChanged(); } }
        [XmlElement("ItemFiltersBindSettings_4")][DefaultValue(true)]
        public bool bindF_4 { get { return _bind[4]; } set { _bind[4] = value; OnFiltersChanged(); } }
        #endregion

        #region ItemFilters by Source
        public List<ItemFilterEnabledOverride> ItemFilterEnabledOverride { get; set; }

        private void SaveItemFilterEnabledOverride()
        {
            ItemFilterEnabledOverride = new List<ItemFilterEnabledOverride>();
            foreach (var itemFilter in ItemFilter.FilterList)
            {
                SaveItemFilterEnabledOverride(itemFilter, ItemFilterEnabledOverride);
            }
            ItemFilterEnabledOverride.Add(new ItemFilterEnabledOverride() { Name = "Other", Enabled = ItemFilter.OtherEnabled });
        }

        private void SaveItemFilterEnabledOverride(ItemFilterRegex itemFilter, List<ItemFilterEnabledOverride> list)
        {
            ItemFilterEnabledOverride filterOverride = new ItemFilterEnabledOverride();
            filterOverride.Name = itemFilter.Name;
            filterOverride.Enabled = itemFilter.Enabled;
            if (itemFilter.RegexList.Count > 0)
            {
                filterOverride.SubFilterOverride = new List<ItemFilterEnabledOverride>();
                foreach (var subFilter in itemFilter.RegexList)
                {
                    SaveItemFilterEnabledOverride(subFilter, filterOverride.SubFilterOverride);
                }
                filterOverride.SubFilterOverride.Add(new ItemFilterEnabledOverride() { Name = "Other", Enabled = itemFilter.OtherRegexEnabled });                
            }
            list.Add(filterOverride);
        }

        public bool LoadItemFilterEnabledOverride()
        {
            bool triggerEvent = false;
            if (ItemFilterEnabledOverride == null || ItemFilterEnabledOverride.Count == 0) return false;
            ItemFilter.IsLoading = true;
            foreach (var filterOverride in ItemFilterEnabledOverride)
            {
                if (filterOverride.Name != "Other")
                {
                    LoadItemFilterEnabledOverride(filterOverride, ItemFilter.FilterList, ref triggerEvent);
                }
                else
                {
                    if (ItemFilter.OtherEnabled != filterOverride.Enabled)
                    {
                        ItemFilter.OtherEnabled = (bool)filterOverride.Enabled;
                        triggerEvent = true;
                    }
                }
            }
            ItemFilter.IsLoading = false;
            return triggerEvent;
        }

        private void LoadItemFilterEnabledOverride(ItemFilterEnabledOverride filterOverride, ItemFilterRegexList list, ref bool triggerEvent)
        {
            foreach (ItemFilterRegex itemFilter in list)
            {
                if (itemFilter.Name == filterOverride.Name)
                {
                    if (itemFilter.Enabled != filterOverride.Enabled)
                    {
                        itemFilter.Enabled = filterOverride.Enabled;
                        triggerEvent = true;
                    }
                    if (filterOverride.SubFilterOverride != null && filterOverride.SubFilterOverride.Count > 0)
                    {
                        foreach (var subOverride in filterOverride.SubFilterOverride)
                        {
                            if (subOverride.Name != "Other")
                            {
                                LoadItemFilterEnabledOverride(subOverride, itemFilter.RegexList, ref triggerEvent);
                            }
                            else
                            {
                                if (itemFilter.OtherRegexEnabled != subOverride.Enabled)
                                {
                                    itemFilter.OtherRegexEnabled = (bool)subOverride.Enabled;
                                    triggerEvent = true;
                                }
                            }
                        }
                    }
                    return;
                }
            }
        }
        #endregion
        #endregion
        #region Settings: Custom Gemming Templates
        public List<GemmingTemplate> CustomGemmingTemplates { get; set; }
        public List<GemmingTemplate> GemmingTemplateOverrides { get; set; }

        private string gemmingTemplateModel;
        private List<GemmingTemplate> currentGemmingTemplates;


        [XmlIgnore]
        public List<GemmingTemplate> CurrentGemmingTemplates
        {
            get
            {
                if (currentGemmingTemplates == null || CurrentModel != gemmingTemplateModel)
                {
                    SaveGemmingTemplateOverrides();
                    GenerateGemmingTemplates();
                }
                return currentGemmingTemplates;
            }
        }

        private void SaveGemmingTemplateOverrides()
        {
            if (currentGemmingTemplates == null) return;
            List<GemmingTemplate> defaults = GemmingTemplate.AllTemplates[gemmingTemplateModel];
            GemmingTemplateOverrides.RemoveAll(template => template.Model == gemmingTemplateModel);
            foreach (GemmingTemplate template in defaults)
            {
                foreach (GemmingTemplate overrideTemplate in currentGemmingTemplates)
                {
                    if (template.Group == overrideTemplate.Group
                        && template.Model == overrideTemplate.Model
                        && template.ArmoringId == overrideTemplate.ArmoringId
                        && template.BarrelId == overrideTemplate.BarrelId
                        && template.HiltId == overrideTemplate.HiltId
                        && template.ModId == overrideTemplate.ModId
                        && template.EnhancementId == overrideTemplate.EnhancementId
                        && template.ColorCrystalId == overrideTemplate.ColorCrystalId
                        && template.AugmentId == overrideTemplate.AugmentId
                        )
                    {
                        if (template.Enabled != overrideTemplate.Enabled)
                        {
                            GemmingTemplateOverrides.Add(overrideTemplate);
                            break;
                        }
                    }
                }
            }
        }

        private void GenerateGemmingTemplates()
        {
            List<GemmingTemplate> defaults = GemmingTemplate.GetModelTemplates(CurrentCalculations); // this can be called while character is loading and current instance might still be set to a different model (i.e. bear)
            currentGemmingTemplates = new List<GemmingTemplate>();
            foreach (GemmingTemplate template in defaults)
            {
                GemmingTemplate toCopy = template;
                foreach (GemmingTemplate overrideTemplate in GemmingTemplateOverrides)
                {
                    if (template.Group == overrideTemplate.Group
                        && template.Model == overrideTemplate.Model
                        && template.ArmoringId == overrideTemplate.ArmoringId
                        && template.BarrelId == overrideTemplate.BarrelId
                        && template.HiltId == overrideTemplate.HiltId
                        && template.ModId == overrideTemplate.ModId
                        && template.EnhancementId == overrideTemplate.EnhancementId
                        && template.ColorCrystalId == overrideTemplate.ColorCrystalId
                        && template.AugmentId == overrideTemplate.AugmentId
                        )
                    {
                        toCopy = overrideTemplate;
                        break;
                    }
                }
                currentGemmingTemplates.Add(new GemmingTemplate()
                {
                    Enabled = toCopy.Enabled,
                    Group = toCopy.Group,
                    Model = toCopy.Model,
                    ArmoringId = toCopy.ArmoringId,
                    BarrelId = toCopy.BarrelId,
                    HiltId = toCopy.HiltId,
                    ModId = toCopy.ModId,
                    EnhancementId = toCopy.EnhancementId,
                    ColorCrystalId = toCopy.ColorCrystalId,
                    AugmentId = toCopy.AugmentId,
                });
            }
            gemmingTemplateModel = CurrentModel;
        }
        #endregion
        #region Settings: Optimizer
        #region Available Items, Green/Blue Diamonds
        /// <summary>The list of items marked as avaiable to the optimizer (Green/Blue Diamonds)</summary>
        [XmlElement("AvailableItems")]
        public List<string> _availableItems;
        /// <summary>list of 5-tuples itemid.gem1id.gem2id.gem3id.enchantid,
        /// itemid is required, others can use * for wildcard
        /// for backward compatibility use just itemid instead of itemid.*.*.*.*
        /// -id represents enchants
        /// </summary>
        [XmlIgnore]
        public List<string> AvailableItems { get { return _availableItems; } set { _availableItems = value; OnAvailableItemsChanged("All", ItemAvailability.NotAvailable); } }
        #region Getting/Setting Item Availability
        public ItemAvailability GetItemAvailability(Item item)
        {
            return GetItemAvailability(item.Id, item.Id + ".0.0.0.0.0.0");
        }
        public ItemAvailability GetItemAvailability(ItemInstance itemInstance)
        {
            return GetItemAvailability(itemInstance.Id.ToString(), itemInstance.ItemModdedId);
        }
        private ItemAvailability GetItemAvailability(string id, string fullId)
        {
            List<string> list = _availableItems.FindAll(x => x.StartsWith(id, StringComparison.Ordinal));
            if (list.Contains(fullId))
            {
                return ItemAvailability.Available;
            }
            if (list.Contains(id))
            {
                return ItemAvailability.RemoddingAllowed;
            }
            else
            {
                return ItemAvailability.NotAvailable;
            }
        }
        public void ToggleItemAvailability(int itemId, bool regemmingAllowed)
        {
            string id = itemId.ToString();
            string anyGem = id + ".*.*.*";
            ItemAvailability newItemAvailability = ItemAvailability.NotAvailable;

            if (id.StartsWith("-", StringComparison.Ordinal) || regemmingAllowed)
            {
                // all enabled toggle
                if (_availableItems.Contains(id) || _availableItems.FindIndex(x => x.StartsWith(anyGem, StringComparison.Ordinal)) >= 0) {
                    _availableItems.Remove(id);
                    _availableItems.RemoveAll(x => x.StartsWith(anyGem, StringComparison.Ordinal));
                    newItemAvailability = ItemAvailability.NotAvailable;
                } else {
                    _availableItems.Add(id);
                    newItemAvailability = ItemAvailability.RemoddingAllowed;
                }
            }
            OnAvailableItemsChanged(id, newItemAvailability);
        }
        public void ToggleItemAvailability(Item item, bool regemmingAllowed)
        {
            string id = item.Id.ToString();
            string anyGem = id + ".*.*.*";
            ItemAvailability newItemAvailability = ItemAvailability.NotAvailable;

            if (id.StartsWith("-", StringComparison.Ordinal) || regemmingAllowed || item.IsItemModification)
            {
                // all enabled toggle
                if (_availableItems.Contains(id) || _availableItems.FindIndex(x => x.StartsWith(anyGem, StringComparison.Ordinal)) >= 0) {
                    _availableItems.Remove(id);
                    _availableItems.RemoveAll(x => x.StartsWith(anyGem, StringComparison.Ordinal));
                    newItemAvailability = ItemAvailability.NotAvailable;
                } else {
                    _availableItems.Add(id);
                    newItemAvailability = ItemAvailability.RemoddingAllowed;
                }
            }
            OnAvailableItemsChanged(id, newItemAvailability);
        }
        public void ToggleItemAvailability(ItemInstance item, bool regemmingAllowed)
        {
            string id = item.Id.ToString();
            ItemAvailability newItemAvailability = ItemAvailability.NotAvailable;

            if (id.StartsWith("-", StringComparison.Ordinal) || regemmingAllowed)
            {
                // all enabled toggle
                if (_availableItems.Contains(id))
                {
                    _availableItems.Remove(id);
                    newItemAvailability = ItemAvailability.NotAvailable;
                }
                else
                {
                    _availableItems.Add(id);
                    newItemAvailability = ItemAvailability.RemoddingAllowed;
                }
            }
            else
            {
                Predicate<string> p = (x =>
                {
                    return x == item.ItemModdedId;
                });
                // enabled toggle
                if (_availableItems.FindIndex(p) >= 0)
                {
                    _availableItems.RemoveAll(p);
                    newItemAvailability = ItemAvailability.NotAvailable;
                }
                else
                {
                    _availableItems.Add(item.ItemModdedId);
                    newItemAvailability = ItemAvailability.Available;
                }
            }
            OnAvailableItemsChanged(id, newItemAvailability);
        }
        #endregion
        public delegate void AvailableItemsChangedEventHandler(object sender, AvailItemsChangedEventArgs fe);
        public event AvailableItemsChangedEventHandler AvailableItemsChanged;
        public void OnAvailableItemsChanged(string thingChanging, ItemAvailability newAvailability)
        {
            if (thingChanging.StartsWith("-")) {
                ItemSlot slot = ItemSlot.None;
                // Wipe out the stored relevants list for this slot
                _relevantItemInstances.Remove(GetCharacterSlotByItemSlot(slot));
            }
            if (AvailableItemsChanged != null)
                AvailableItemsChanged(this, new AvailItemsChangedEventArgs(thingChanging, newAvailability));
        }
        public class AvailItemsChangedEventArgs : EventArgs {
            public AvailItemsChangedEventArgs(string thingChanging, ItemAvailability newAvailability) {
                ThingChanging = thingChanging;
                NewAvailability = newAvailability;
            }
            public string ThingChanging;
            public ItemAvailability NewAvailability;
        }
        #endregion
        [XmlIgnore]
        private List<ItemInstance> _customItemInstances;
        public List<ItemInstance> CustomItemInstances
        {
            get { return _customItemInstances; }
            set {
                _customItemInstances = value;
                InvalidateItemInstances();
            }
        }
        /// <summary>In the optimizer, you can choose to optimize a subpoint rather than Overall</summary>
        public string CalculationToOptimize { get; set; }
        /// <summary>Any requirements you have set on the optimizer</summary>
        public List<OptimizationRequirement> OptimizationRequirements { get; set; }
        #endregion

        #region Functions: Get/Set All Items on the character, primarily used by the Optimizer
        public ItemInstance[] GetItems()
        {
            return (ItemInstance[])_item.Clone();
        }
        public void SetItems(ItemInstance[] items) { SetItems(items, true); }
        public void SetItems(ItemInstance[] items, bool invalidate)
        {
            int max = Math.Min(OptimizableSlotCount, items.Length);
            for (int slot = 0; slot < max; slot++)
            {
                _item[slot] = items[slot] == null ? null : items[slot].Clone();
            }
            // when called from optimizer we never want to invalidate since that causes creation of new item instances
            // and causes us to lose stats cache
            if (invalidate) { OnCalculationsInvalidated(); }
        }
        public void SetItems(Character character) { SetItems(character, false, true); }
        public void SetItems(Character character, bool allSlots, bool invalidate)
        {
            int max = allSlots ? SlotCount : OptimizableSlotCount;
            for (int slot = 0; slot < max; slot++)
            {
                _item[slot] = character._item[slot] == null ? null : character._item[slot].Clone();
            }
            // when called from optimizer we never want to invalidate since that causes creation of new item instances
            // and causes us to lose stats cache
            if (invalidate) { OnCalculationsInvalidated(); }
        }
        #endregion

        #region Static Functions, used for Validations or Conversions
        public static CharacterSlot GetCharacterSlotByItemSlot(ItemSlot slot)
        {
            
            //note: When converting ItemSlot.Finger and ItemSlot.Trinket, this will ALWAYS
            //place them in Slot 1 of the 2 possibilities. Items listed as OneHand or TwoHand 
            //in their Itemslot profile, will be parsed into the MainHand CharacterSlot.
            
            switch (slot)
            {
               
                case Rage.ItemSlot.Head: return CharacterSlot.Head;
                case Rage.ItemSlot.Chest: return CharacterSlot.Chest;
                case Rage.ItemSlot.Hands: return CharacterSlot.Hands;
                case Rage.ItemSlot.Waist: return CharacterSlot.Waist;
                case Rage.ItemSlot.Legs: return CharacterSlot.Legs;
                case Rage.ItemSlot.Feet: return CharacterSlot.Feet;

                case Rage.ItemSlot.Ear: return CharacterSlot.Ear;
                case Rage.ItemSlot.Implant: return CharacterSlot.Implant1;
                //case Rage.ItemSlot.Implant: return CharacterSlot.Implant2;
                case Rage.ItemSlot.Wrist: return CharacterSlot.Wrist;
                case Rage.ItemSlot.Relic: return CharacterSlot.Relic1;
                //case Rage.ItemSlot.Relic: return CharacterSlot.Relic2;

                case Rage.ItemSlot.MainHand: return CharacterSlot.MainHand;
                case Rage.ItemSlot.OffHand: return CharacterSlot.OffHand;
                case Rage.ItemSlot.OneHand: return CharacterSlot.MainHand;

                case Rage.ItemSlot.Armoring: return CharacterSlot.Armoring;
                case Rage.ItemSlot.Hilt: return CharacterSlot.Hilt;
                case Rage.ItemSlot.Barrel: return CharacterSlot.Barrel;
                case Rage.ItemSlot.Mod: return CharacterSlot.Mod;
                case Rage.ItemSlot.Enhancement: return CharacterSlot.Enhancement;
                case Rage.ItemSlot.ColorCrystal: return CharacterSlot.ColorCrystal;
                case Rage.ItemSlot.Augment: return CharacterSlot.Augment;

                default: return CharacterSlot.None;
            }
        }
        #endregion

        #region Internal Variables used in calculations, These should not be saved to XML
        [XmlIgnore]
        internal ItemInstance[] _item;
        // Set to true to suppress ItemsChanged event
        [XmlIgnore]
        public bool IsLoading { get; set; }
        [XmlIgnore]
        public CalculationsBase CurrentCalculations { get { return Calculations.GetModel(CurrentModel); } }
        [XmlIgnore]
        public bool DisableBuffAutoActivation { get; set; }
        #endregion

        #region Items in Slots
        public int MaxWornItemLevel {
            get {
                int retVal = 0;
                foreach (CharacterSlot slot in EquippableCharacterSlots) {
                    if (this[slot] != null && this[slot].Item.ItemLevel > retVal) {
                        retVal = this[slot].Item.ItemLevel;
                    }
                }
                return retVal;
            }
        }
        public int MinWornItemLevel {
            get {
                int retVal = 10000;
                foreach (CharacterSlot slot in EquippableCharacterSlots) {
                    if (this[slot] != null && this[slot].Item.ItemLevel < retVal)
                    {
                        retVal = this[slot].Item.ItemLevel;
                    }
                }
                return retVal;
            }
        }
        public int AvgWornItemLevel {
            get {
                int retVal = 0;
                int count = 0;
                foreach (CharacterSlot slot in EquippableCharacterSlots) {
                    if (this[slot] != null)
                    {
                        retVal += this[slot].Item.ItemLevel;
                        count++;
                    }
                }
                //return count != 0 ? (int)Math.Round(((double)retVal / (double)count), MidpointRounding.AwayFromZero) : 0;  //MidpointRounding does not exist in Silverlight
                return count != 0 ? (int)((double)retVal / (double)count) : 0;
            }
        }

        [XmlIgnore]
        public ItemInstance Head { get { return this[CharacterSlot.Head]; } set { this[CharacterSlot.Head] = value; } }
        [XmlIgnore]
        public ItemInstance Chest { get { return this[CharacterSlot.Chest]; } set { this[CharacterSlot.Chest] = value; } }
        [XmlIgnore]
        public ItemInstance Hands { get { return this[CharacterSlot.Hands]; } set { this[CharacterSlot.Hands] = value; } }
        [XmlIgnore]
        public ItemInstance Waist { get { return this[CharacterSlot.Waist]; } set { this[CharacterSlot.Waist] = value; } }
        [XmlIgnore]
        public ItemInstance Legs { get { return this[CharacterSlot.Legs]; } set { this[CharacterSlot.Legs] = value; } }
        [XmlIgnore]
        public ItemInstance Feet { get { return this[CharacterSlot.Feet]; } set { this[CharacterSlot.Feet] = value; } }
        [XmlIgnore]
        public ItemInstance Ear { get { return this[CharacterSlot.Ear]; } set { this[CharacterSlot.Ear] = value; } }
        [XmlIgnore]
        public ItemInstance Implant1 { get { return this[CharacterSlot.Implant1]; } set { this[CharacterSlot.Implant1] = value; } }
        [XmlIgnore]
        public ItemInstance Implant2 { get { return this[CharacterSlot.Implant2]; } set { this[CharacterSlot.Implant2] = value; } }
        [XmlIgnore]
        public ItemInstance Wrist { get { return this[CharacterSlot.Wrist]; } set { this[CharacterSlot.Wrist] = value; } }
        [XmlIgnore]
        public ItemInstance Relic1 { get { return this[CharacterSlot.Relic1]; } set { this[CharacterSlot.Relic1] = value; } }
        [XmlIgnore]
        public ItemInstance Relic2 { get { return this[CharacterSlot.Relic2]; } set { this[CharacterSlot.Relic2] = value; } }
        [XmlIgnore]
        public ItemInstance MainHand { get { return this[CharacterSlot.MainHand]; } set { this[CharacterSlot.MainHand] = value; } }
        [XmlIgnore]
        public ItemInstance OffHand { get { return this[CharacterSlot.OffHand]; } set { this[CharacterSlot.OffHand] = value; } }
        #endregion

        public void InvalidateItemInstances()
        {
            if (_relevantItems != null)
            {
                _relevantItems.Clear();
                _relevantItemInstances.Clear();
            }
        }

        public void InvalidateItemInstances(CharacterSlot slot)
        {
            _relevantItemInstances.Remove(slot);
        }

        [XmlIgnore]
        private Dictionary<CharacterSlot, List<ItemInstance>> _relevantItemInstances;

        [XmlIgnore]
        private Dictionary<CharacterSlot, List<Item>> _relevantItems;

        public List<ItemInstance> GetRelevantItemInstances(CharacterSlot slot, bool forceAll=false)
        {
            bool blacksmithingSocket = false;
            if ((slot == CharacterSlot.Head && HeadAugmentSocketEnabled)
                || (slot == CharacterSlot.Chest && ChestAugmentSocketEnabled)
                || (slot == CharacterSlot.Hands && HandsAugmentSocketEnabled)
                || (slot == CharacterSlot.Waist && WaistAugmentSocketEnabled)
                || (slot == CharacterSlot.Legs && LegsAugmentSocketEnabled)
                || (slot == CharacterSlot.Feet && FeetAugmentSocketEnabled)
                || (slot == CharacterSlot.Ear && EarAugmentSocketEnabled)
                || (slot == CharacterSlot.Implant1 && Implant1AugmentSocketEnabled)
                || (slot == CharacterSlot.Implant2 && Implant2AugmentSocketEnabled)
                || (slot == CharacterSlot.Wrist && WristAugmentSocketEnabled)
                || (slot == CharacterSlot.Relic1 && Relic1AugmentSocketEnabled)
                || (slot == CharacterSlot.Relic2 && Relic2AugmentSocketEnabled)
                || (slot == CharacterSlot.MainHand && MainHandAugmentSocketEnabled)
                || (slot == CharacterSlot.OffHand && OffHandAugmentSocketEnabled))
            {
                blacksmithingSocket = true;
            }
            List<ItemInstance> items;
            if (!_relevantItemInstances.TryGetValue(slot, out items))
            {
                Dictionary<string, bool> itemChecked = new Dictionary<string, bool>();
                items = new List<ItemInstance>();
                foreach (Item item in (forceAll ? ItemCache.AllItems : ItemCache.RelevantItems))
                {
                    if (item.FitsInSlot(slot, this) && item.FitsFaction(Race) && item.FitsLightDarkReq(LightDark))
                    {
                        itemChecked[item.Id] = true;
                        List<int> suffixList;
                        if (item.AllowedRandomSuffixes == null || item.AllowedRandomSuffixes.Count == 0) {
                            suffixList = zeroSuffixList;
                        } else {
                            suffixList = item.AllowedRandomSuffixes;
                        }
                        foreach (int randomSuffix in suffixList)
                        {
                            List<ItemInstance> itemInstances = new List<ItemInstance>();
                            // Built in Gemming Templates
                            foreach (GemmingTemplate template in CurrentGemmingTemplates)
                            {
                                if (template.Enabled)
                                {
                                    ItemInstance instance =
                                        template.GetItemInstance(item, randomSuffix, blacksmithingSocket);
                                    if (!itemInstances.Contains(instance)) itemInstances.Add(instance);
                                }
                            }
                            // Gemming Templates the User added
                            foreach (GemmingTemplate template in CustomGemmingTemplates)
                            {
                                if (template.Enabled && template.Model == CurrentModel)
                                {
                                    ItemInstance instance = template.GetItemInstance(item, randomSuffix, blacksmithingSocket);
                                    if (!itemInstances.Contains(instance)) itemInstances.Add(instance);
                                }
                            }
                            items.AddRange(itemInstances);
                        }
                    }
                }
                // add custom instances
                foreach (ItemInstance item in CustomItemInstances)
                {
                    if (item.Item != null && item.Item.FitsInSlot(slot, this)) // item.Item can be null if you're loading character with custom items that are not present on this install
                    {
                        // if it's already in make sure to set force visible to true
                        int index = items.IndexOf(item);
                        if (index >= 0)
                        {
                            items[index] = item;
                        }
                        else
                        {
                            items.Add(item);
                        }
                    }
                }
                // add available instances (green diamonds)
                foreach (string availableItem in AvailableItems)
                {
                    string[] ids = availableItem.Split('.');
                    if (ids.Length <= 2)
                    {
                        // we have an available item that might be filtered out
                        Item item = ItemCache.FindItemById(ids[0]);
                        if (item != null)
                        {
                            if (item.FitsInSlot(slot, this))
                            {
                                if (itemChecked.ContainsKey(item.Id))
                                {
                                    // we've already processed this one
                                    continue;
                                }
                                List<ItemInstance> itemInstances = new List<ItemInstance>();
                                int randomSuffix = ids.Length < 2 ? 0 : int.Parse(ids[1], System.Globalization.CultureInfo.InvariantCulture);
                                foreach (GemmingTemplate template in CurrentGemmingTemplates)
                                {
                                    if (template.Enabled)
                                    {
                                        ItemInstance instance = template.GetItemInstance(item, randomSuffix, blacksmithingSocket);
                                        if (!itemInstances.Contains(instance)) itemInstances.Add(instance);
                                    }
                                }
                                foreach (GemmingTemplate template in CustomGemmingTemplates)
                                {
                                    if (template.Enabled && template.Model == CurrentModel)
                                    {
                                        ItemInstance instance = template.GetItemInstance(item, randomSuffix, blacksmithingSocket);
                                        if (!itemInstances.Contains(instance)) itemInstances.Add(instance);
                                    }
                                }
                                items.AddRange(itemInstances);
                            }
                            itemChecked[item.Id] = true;
                        }
                    }
                }
                // add available instances (blue diamonds)
                foreach (string availableItem in AvailableItems)
                {
                    string[] ids = availableItem.Split('.');//{id}.{rs}.{im1}.{im2}.{im3}.{im4}.{im5}
                    if (ids.Length == 7) // only support new format with random suffixes
                    {
                        Item item = ItemCache.FindItemById(ids[0]);
                        if (item.FitsInSlot(slot, this))
                        {
                            ItemInstance instance = new ItemInstance(availableItem);
                            instance.ForceDisplay = true;
                            // we want to force display even if it's already present (might be lower than top N)
                            int index = items.IndexOf(instance);
                            if (index < 0)
                            {
                                items.Add(instance);
                            }
                            else
                            {
                                items[index] = instance;
                            }
                        }
                    }
                } 
                _relevantItemInstances[slot] = items;
            }
            return items;
        }

        public void ClearRelevantItemModifications()
        {
            _relevantItems.Remove(CharacterSlot.Armoring);
            _relevantItems.Remove(CharacterSlot.Barrel);
            _relevantItems.Remove(CharacterSlot.Hilt);
            _relevantItems.Remove(CharacterSlot.Mod);
            _relevantItems.Remove(CharacterSlot.Enhancement);
            _relevantItems.Remove(CharacterSlot.ColorCrystal);
            _relevantItems.Remove(CharacterSlot.Augment);
        }
        public List<Item> GetRelevantItems(CharacterSlot slot) { return GetRelevantItems(slot, ItemSlot.None); }
        public List<Item> GetRelevantItems(CharacterSlot slot, ItemSlot gemColour)
        {
            List<Item> items;
            if (!_relevantItems.TryGetValue(slot, out items))
            {
                items = new List<Item>();
                foreach (Item item in ItemCache.RelevantItems)
                {
                    if (item.FitsInSlot(slot, this))
                    {
                        if ((gemColour == ItemSlot.None) ||
                            (gemColour == ItemSlot.Armoring && item.IsArmoring) ||
                            (gemColour == ItemSlot.Barrel && item.IsBarrel) ||
                            (gemColour == ItemSlot.Hilt && item.IsHilt) ||
                            (gemColour == ItemSlot.Mod && item.IsMod) ||
                            (gemColour == ItemSlot.Enhancement && item.IsEnhancement) ||
                            (gemColour == ItemSlot.ColorCrystal && item.IsColorCrystal) ||
                            (gemColour == ItemSlot.Augment && item.IsAugment))
                        {
                            items.Add(item);
                        }
                    }
                }
                _relevantItems[slot] = items;
            }
            return items;
        }

        public void AssignAllTalentsFromCharacter(Character character, bool clone)
        {
            if (clone) {
                // Imperial
                JuggernautTalents = (JuggernautTalents)character.JuggernautTalents.Clone();
                MarauderTalents = (MarauderTalents)character.MarauderTalents.Clone();
                AssassinTalents = (AssassinTalents)character.AssassinTalents.Clone();
                SorcererTalents = (SorcererTalents)character.SorcererTalents.Clone();
                PowertechTalents = (PowertechTalents)character.PowertechTalents.Clone();
                MercenaryTalents = (MercenaryTalents)character.MercenaryTalents.Clone();
                OperativeTalents = (OperativeTalents)character.OperativeTalents.Clone();
                SniperTalents = (SniperTalents)character.SniperTalents.Clone();
                // Republic
                GuardianTalents = (GuardianTalents)character.GuardianTalents.Clone();
                SentinelTalents = (SentinelTalents)character.SentinelTalents.Clone();
                ShadowTalents = (ShadowTalents)character.ShadowTalents.Clone();
                SageTalents = (SageTalents)character.SageTalents.Clone();
                VanguardTalents = (VanguardTalents)character.VanguardTalents.Clone();
                CommandoTalents = (CommandoTalents)character.CommandoTalents.Clone();
                ScoundrelTalents = (ScoundrelTalents)character.ScoundrelTalents.Clone();
                GunslingerTalents = (GunslingerTalents)character.GunslingerTalents.Clone();
            } else {
                // Imperial
                _JuggernautTalents = character._JuggernautTalents;
                _MarauderTalents = character._MarauderTalents;
                _AssassinTalents = character._AssassinTalents;
                _SorcererTalents = character._SorcererTalents;
                _PowertechTalents = character._PowertechTalents;
                _MercenaryTalents = character._MercenaryTalents;
                _OperativeTalents = character._OperativeTalents;
                _SniperTalents = character._SniperTalents;
                // Republic
                _GuardianTalents = character._GuardianTalents;
                _SentinelTalents = character._SentinelTalents;
                _ShadowTalents = character._ShadowTalents;
                _SageTalents = character._SageTalents;
                _VanguardTalents = character._VanguardTalents;
                _CommandoTalents = character._CommandoTalents;
                _ScoundrelTalents = character._ScoundrelTalents;
                _GunslingerTalents = character._GunslingerTalents;
            }
        }

        public bool IsEquipped(ItemInstance itemToBeChecked)
        {
            CharacterSlot slot = Character.GetCharacterSlotByItemSlot(itemToBeChecked.Slot);
            if (slot == CharacterSlot.Implant1)
                return IsEquipped(itemToBeChecked, CharacterSlot.Implant1) || IsEquipped(itemToBeChecked, CharacterSlot.Implant2);
            else if (itemToBeChecked.Slot == Rage.ItemSlot.OneHand)
                return IsEquipped(itemToBeChecked, CharacterSlot.MainHand) || IsEquipped(itemToBeChecked, CharacterSlot.OffHand);
            else if (itemToBeChecked.Slot == Rage.ItemSlot.Relic)
                return IsEquipped(itemToBeChecked, CharacterSlot.Relic1) || IsEquipped(itemToBeChecked, CharacterSlot.Relic2);
            else
                return IsEquipped(itemToBeChecked, slot);
        }
        public bool IsEquipped(ItemInstance itemToBeChecked, CharacterSlot slot)
        {
            return itemToBeChecked == this[slot];
        }
        public bool IsEquipped(Item itemToBeChecked)
        {
            CharacterSlot slot = Character.GetCharacterSlotByItemSlot(itemToBeChecked.Slot);
            if (slot == CharacterSlot.Implant1)
                return IsEquipped(itemToBeChecked, CharacterSlot.Implant1) || IsEquipped(itemToBeChecked, CharacterSlot.Implant2);
            else if (itemToBeChecked.Slot == Rage.ItemSlot.OneHand)
                return IsEquipped(itemToBeChecked, CharacterSlot.MainHand) || IsEquipped(itemToBeChecked, CharacterSlot.OffHand);
            else if (itemToBeChecked.Slot == Rage.ItemSlot.Relic)
                return IsEquipped(itemToBeChecked, CharacterSlot.Relic1) || IsEquipped(itemToBeChecked, CharacterSlot.Relic2);
            else
                return IsEquipped(itemToBeChecked, slot);
        }
        public bool IsEquipped(Item itemToBeChecked, CharacterSlot slot)
        {
            return (object)this[slot] != null && itemToBeChecked.Id == this[slot].Id;
        }

        public event EventHandler CalculationsInvalidated;
        public void OnCalculationsInvalidated()
        {
#if DEBUG
            if (CalculationsInvalidated != null) System.Diagnostics.Debug.WriteLine("Starting CalculationsInvalidated: " + this.Name);
            DateTime start = DateTime.Now;
#endif
            InvalidateItemInstances();
            if (IsLoading) {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("CalculationsInvalidated: " + this.Name +" Skipped: Character Is Loading");
#endif
                return;
            }
            RecalculateSetBonuses();

            if (CalculationsInvalidated != null)
            {
                CalculationsInvalidated(this, EventArgs.Empty);
#if DEBUG
                System.Diagnostics.Debug.WriteLine("Finished CalculationsInvalidated: Total " + DateTime.Now.Subtract(start).TotalMilliseconds.ToString() + "ms");
#endif
            }
        }

        private Dictionary<string, int> _setBonusCount = null;
        [XmlIgnore]
        public Dictionary<string, int> SetBonusCount {
            get {
                if (_setBonusCount == null)
                {
                    RecalculateSetBonuses();
                }
                return _setBonusCount;
            }
            private set { _setBonusCount = value; }
        }

        public void RecalculateSetBonuses()
        {
            if (_setBonusCount == null) {
                SetBonusCount = new Dictionary<string, int>();
            } else {
                SetBonusCount.Clear();
            }
            //Compute Set Bonuses
            for (int slot = 0; slot < _item.Length; slot++)
            {
                ItemInstance item = _item[slot];
                if ((object)item != null)
                {
                    Item i = item.Item;
                    if (i != null && !string.IsNullOrEmpty(i.SetName))
                    {
                        int count;
                        SetBonusCount.TryGetValue(i.SetName, out count);
                        SetBonusCount[i.SetName] = count + 1;
                    }
                }
            }
        }

        [XmlIgnore]
        public ItemInstance this[CharacterSlot slot]
        {
            get
            {
                int i = (int)slot;
                if (i < 0 || i >= SlotCount) return null;
                return _item[i];
            }
            set
            {
                int i = (int)slot;
                if (i < 0 || i >= SlotCount) return;
                // should we track id changes? for now assume assume we don't have to
                _item[i] = value;
                OnCalculationsInvalidated();
            }
        }

        public string[] GetAllEquippedGearIds()
        {
            Dictionary<string, bool> _ids = new Dictionary<string, bool>();
            if (_head != null) _ids[_head] = true;
            if (_chest != null) _ids[_chest] = true;
            if (_waist != null) _ids[_waist] = true;
            if (_hands != null) _ids[_hands] = true;
            if (_legs != null) _ids[_legs] = true;
            if (_feet != null) _ids[_feet] = true;
            if (_ear != null) _ids[_ear] = true;
            if (_implant1 != null) _ids[_implant1] = true;
            if (_implant2 != null) _ids[_implant2] = true;
            if (_wrist != null) _ids[_wrist] = true;
            if (_relic1 != null) _ids[_relic1] = true;
            if (_relic2 != null) _ids[_relic2] = true;
            if (_mainHand != null) _ids[_mainHand] = true;
            if (_offHand != null) _ids[_offHand] = true;
            return new List<string>(_ids.Keys).ToArray();
        }
        public string[] GetAllEquippedAndAvailableGearIds()
        {
            Dictionary<string, bool> _ids = new Dictionary<string, bool>();
            if (_head != null) _ids[_head] = true;
            if (_chest != null) _ids[_chest] = true;
            if (_hands != null) _ids[_hands] = true;
            if (_waist != null) _ids[_waist] = true;
            if (_legs != null) _ids[_legs] = true;
            if (_feet != null) _ids[_feet] = true;
            if (_ear != null) _ids[_ear] = true;
            if (_implant1 != null) _ids[_implant1] = true;
            if (_implant2 != null) _ids[_implant2] = true;
            if (_wrist != null) _ids[_wrist] = true;
            if (_relic1 != null) _ids[_relic1] = true;
            if (_relic2 != null) _ids[_relic2] = true;
            if (_mainHand != null) _ids[_mainHand] = true;
            if (_offHand != null) _ids[_offHand] = true;
            foreach (string xid in AvailableItems)
            {
                if (!xid.StartsWith("-", StringComparison.Ordinal))
                {
                    int dot = xid.LastIndexOf('.');
                    _ids[(dot >= 0) ? xid.Substring(0, dot).Replace(".*.*.*", "") : xid] = true;
                }
            }
            return new List<string>(_ids.Keys).ToArray();
        }

        public CharacterSlot[] GetEquippedSlots(ItemInstance item)
        {
            List<CharacterSlot> listSlots = new List<CharacterSlot>();
            foreach (CharacterSlot slot in CharacterSlots)
                if (this[slot] == item)
                    listSlots.Add(slot);
            return listSlots.ToArray();
        }

        public static CharacterSlot GetCharacterSlotFromId(int slotId)
        {
            CharacterSlot cslot = CharacterSlot.None;
            switch (slotId)
            {
                case -1: cslot = CharacterSlot.None; break;

                case 0: cslot = CharacterSlot.Head; break;
                case 1: cslot = CharacterSlot.Chest; break;
                case 2: cslot = CharacterSlot.Hands; break;
                case 3: cslot = CharacterSlot.Waist; break;
                case 4: cslot = CharacterSlot.Legs; break;
                case 5: cslot = CharacterSlot.Feet; break;

                case 6: cslot = CharacterSlot.Ear; break;
                case 7: cslot = CharacterSlot.Implant1; break;
                case 8: cslot = CharacterSlot.Implant2; break;
                case 9: cslot = CharacterSlot.Wrist; break;
                case 10: cslot = CharacterSlot.Relic1; break;
                case 11: cslot = CharacterSlot.Relic2; break;

                case 12: cslot = CharacterSlot.MainHand; break;
                case 13: cslot = CharacterSlot.OffHand; break;

                case 100: cslot = CharacterSlot.Armoring; break;
                case 101: cslot = CharacterSlot.Hilt; break;
                case 102: cslot = CharacterSlot.Barrel; break;
                case 103: cslot = CharacterSlot.Mod; break;
                case 104: cslot = CharacterSlot.Enhancement; break;
                case 105: cslot = CharacterSlot.ColorCrystal; break;
                case 106: cslot = CharacterSlot.Augment; break;
            }
            return cslot;
        }

        #region Character Contructors and Initializers
        private void Initialize()
        {
            // common initialization used by constructors
            // avoid inline instantiation of fields as not all constructors want/need the overhead
            _item = new ItemInstance[SlotCount];
            _availableItems = new List<string>();
            _calculationOptions = new SerializableDictionary<string, ICalculationOptionBase>();
            _customItemInstances = new List<ItemInstance>();
            CustomGemmingTemplates = new List<GemmingTemplate>();
            GemmingTemplateOverrides = new List<GemmingTemplate>();
            _relevantItemInstances = new Dictionary<CharacterSlot, List<ItemInstance>>();
            _relevantItems = new Dictionary<CharacterSlot, List<Item>>();
        }

        public Character() 
        {
            // this constructor is used be deserialization
            // deserialization also sets all kinds of filter bools, make sure they don't trigger a million item cache changed events
            Initialize();
            _activeBuffs = new List<Buff>();
            IsLoading = true;
        }

        public Character(string name, string realm, CharacterRegion region, CharacterRace race, BossOptions boss,
            string head, string chest, string hands, string waist, string legs, string feet,
            string ear, string implant1, string implant2, string wrist, string relic1, string relic2,
            string mainHand, string offHand)
        {
            Initialize();
            IsLoading = true;
            _name = name;
            _realm = realm;
            _region = region;
            _race = race;

            _head = head;
            _chest = chest;
            _hands = hands;
            _waist = waist;
            _legs = legs;
            _feet = feet;
            _ear = ear;
            _implant1 = implant1;
            _implant2 = implant2;
            _wrist = wrist;
            _relic1 = relic1;
            _relic2 = relic2;
            _mainHand = mainHand;
            _offHand = offHand;

            _activeBuffs = new List<Buff>();
            SetFaction();
            IsLoading = false;
            RecalculateSetBonuses();

            BossOptions = boss.Clone();
        }

        public Character(string name, string realm, CharacterRegion region, CharacterRace race, BossOptions boss,
            ItemInstance head, ItemInstance chest, ItemInstance hands, ItemInstance waist, ItemInstance legs, ItemInstance feet,
                ItemInstance ear, ItemInstance implant1, ItemInstance implant2, ItemInstance wrist, ItemInstance relic1, ItemInstance relic2,
            ItemInstance mainHand, ItemInstance offHand)
        {
            Initialize();
            //_trackEquippedItemChanges = trackEquippedItemChanges;
            IsLoading = true;
            _name = name;
            _realm = realm;
            _region = region;
            _race = race;
            Head = head;
            Chest = chest;
            Wrist = wrist;
            Hands = hands;
            Waist = waist;
            Legs = legs;
            Feet = feet;
            Ear = ear;
            Implant1 = implant1;
            Implant2 = implant2;
            Relic1 = relic1;
            Relic2 = relic2;
            MainHand = mainHand;
            OffHand = offHand;
            _activeBuffs = new List<Buff>();
            SetFaction();
            IsLoading = false;
            RecalculateSetBonuses();
            BossOptions = boss.Clone();
        }

        // The following are special constructors used by optimizer, they assume the cached items/enchant are always used, and the underlying gemmedid/enchantid are never used
        public Character(string name, string realm, CharacterRegion region, CharacterRace race, BossOptions boss,
            ItemInstance head, ItemInstance chest,  ItemInstance hands, ItemInstance waist, ItemInstance legs, ItemInstance feet,
            ItemInstance ear, ItemInstance implant1, ItemInstance implant2, ItemInstance wrist, ItemInstance relic1, ItemInstance relic2,
            ItemInstance mainHand, ItemInstance offHand, List<Buff> activeBuffs, string model)
        {
            Initialize();
            IsLoading = true;
            _name = name;
            _realm = realm;
            _region = region;
            _race = race;
            _item[(int)CharacterSlot.Head] = head;
            _item[(int)CharacterSlot.Chest] = chest;
            _item[(int)CharacterSlot.Hands] = hands;
            _item[(int)CharacterSlot.Waist] = waist;
            _item[(int)CharacterSlot.Legs] = legs;
            _item[(int)CharacterSlot.Feet] = feet;
            _item[(int)CharacterSlot.Ear] = ear;
            _item[(int)CharacterSlot.Implant1] = implant1;
            _item[(int)CharacterSlot.Implant2] = implant2;
            _item[(int)CharacterSlot.Wrist] = wrist;
            _item[(int)CharacterSlot.Relic1] = relic1;
            _item[(int)CharacterSlot.Relic2] = relic2;
            _item[(int)CharacterSlot.MainHand] = mainHand;
            _item[(int)CharacterSlot.OffHand] = offHand;
            IsLoading = false;
            ActiveBuffs = new List<Buff>(activeBuffs);
            SetFaction();
            CurrentModel = model;
            RecalculateSetBonuses();

            BossOptions = boss.Clone();
        }

        /// <summary>This overload is used from optimizer and is optimized for performance, do not modify</summary>
        public Character(Character baseCharacter, object[] items, int count)
        {
            IsLoading = true;
            _name = baseCharacter._name;
            _realm = baseCharacter._realm;
            _region = baseCharacter._region;
            _race = baseCharacter._race;
            _currentModel = baseCharacter._currentModel;
            _calculationOptions = baseCharacter._calculationOptions;
            _class = baseCharacter._class;
            AssignAllTalentsFromCharacter(baseCharacter, false);
            CalculationToOptimize = baseCharacter.CalculationToOptimize;
            OptimizationRequirements = baseCharacter.OptimizationRequirements;
            _bossOptions = baseCharacter._bossOptions;
            _faction = baseCharacter._faction;

            _item = new ItemInstance[SlotCount];
            Array.Copy(items, _item, count);

            IsLoading = false;
            ActiveBuffs = new List<Buff>(baseCharacter.ActiveBuffs);
            RecalculateSetBonuses();
        }

        /// <summary>
        /// This is a variant of the above constructor used when recycling Character, assuming
        /// it was first created with the above constructor and same baseCharacter.
        /// </summary>
        internal void InitializeCharacter(object[] items, int count)
        {
            Array.Copy(items, _item, count);
            RecalculateSetBonuses();
        }

        public Character(string name, string realm, CharacterRegion region, CharacterRace race, BossOptions boss,
            ItemInstance[] items, List<Buff> activeBuffs, string model)
        {
            Initialize();
            IsLoading = true;
            _name = name;
            _realm = realm;
            _region = region;
            _race = race;
            Array.Copy(items, _item, items.Length);

            IsLoading = false;
            ActiveBuffs = new List<Buff>(activeBuffs);
            SetFaction();
            CurrentModel = model;
            RecalculateSetBonuses();

            BossOptions = boss.Clone();
        }

        public Character Clone()
        {
            ItemInstance[] clonedItemInstances = new ItemInstance[SlotCount];
            for (int i = 0; i < clonedItemInstances.Length; i++)
            {
                ItemInstance itemInstance = _item[i];
                if (itemInstance != null) clonedItemInstances[i] = itemInstance.Clone();
            }
            Character clone = new Character(this.Name, this.Realm, this.Region, this.Race, this.BossOptions.Clone(),
                clonedItemInstances, ActiveBuffs, CurrentModel);
            clone.CalculationOptions = this.CalculationOptions;
            clone.itemSetList = this.itemSetList;
            clone.Class = this.Class;
            clone.AssignAllTalentsFromCharacter(this, true);
            clone.HeadAugmentSocketEnabled = this.HeadAugmentSocketEnabled;
            clone.ChestAugmentSocketEnabled = this.ChestAugmentSocketEnabled;
            clone.HandsAugmentSocketEnabled = this.HandsAugmentSocketEnabled;
            clone.WaistAugmentSocketEnabled = this.WaistAugmentSocketEnabled;
            clone.LegsAugmentSocketEnabled = this.LegsAugmentSocketEnabled;
            clone.FeetAugmentSocketEnabled = this.FeetAugmentSocketEnabled;
            clone.EarAugmentSocketEnabled = this.EarAugmentSocketEnabled;
            clone.Implant1AugmentSocketEnabled = this.Implant1AugmentSocketEnabled;
            clone.Implant2AugmentSocketEnabled = this.Implant2AugmentSocketEnabled;
            clone.WristAugmentSocketEnabled = this.WristAugmentSocketEnabled;
            clone.Relic1AugmentSocketEnabled = this.Relic1AugmentSocketEnabled;
            clone.Relic2AugmentSocketEnabled = this.Relic2AugmentSocketEnabled;
            clone.MainHandAugmentSocketEnabled = this.MainHandAugmentSocketEnabled;
            clone.OffHandAugmentSocketEnabled = this.OffHandAugmentSocketEnabled;
            clone.OptimizationRequirements = this.OptimizationRequirements;
            clone.CalculationToOptimize = this.CalculationToOptimize;
            clone.BossOptions = this.BossOptions.Clone();
            return clone;
        }
    
        public void Save(Stream stream, bool closeStream = false)
        {
            SerializeCalculationOptions();
            SaveGemmingTemplateOverrides();
            SaveItemFilterEnabledOverride();
            _activeBuffsXml = new List<string>(_activeBuffs.ConvertAll(buff => buff.Name));
            _itemSetListXML = new List<string>(itemSetList.ConvertAll(ItemSet => ItemSet.ToGemmedIDList()));

            XmlSerializer serializer = new XmlSerializer(typeof(Character));
            serializer.Serialize(stream, this);
            if (closeStream) stream.Close();
        }

        public void SaveBuffs(Stream writer)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<Buff>));
            serializer.Serialize(writer, _activeBuffs);
            writer.Close();
        }

        public static Character LoadFromXml(string xml)
        {
            Character character;
            if (!string.IsNullOrEmpty(xml))
            {
                try
                {
                    xml = xml.Replace("<Region>en", "<Region>US").Replace("<Weapon>", "<MainHand>").Replace("</Weapon>", "</MainHand>")
                        .Replace("<Idol>", "<Ranged>").Replace("</Idol>", "</Ranged>").Replace("<WeaponEnchant>", "<MainHandEnchant>").Replace("</WeaponEnchant>", "</MainHandEnchant>")
                        .Replace("HolyPriest", "HealMerc")
                        .Replace("T7_0", "T11_10").Replace("T8_0", "T11_10").Replace("T9_0", "T11_10").Replace("T10_0", "T11_10")
                        .Replace("T7_5", "T11_10").Replace("T8_5", "T11_10").Replace("T9_5", "T11_10").Replace("T10_5", "T11_10")
                        .Replace("T7_9", "T11_10").Replace("T8_9", "T11_10").Replace("T9_9", "T11_10").Replace("T10_9", "T11_10")
                        .Replace("T11_0", "T11_10").Replace("T11_5", "T11_10").Replace("T11_9", "T11_10")
                        .Replace("<Attack xsi:type=\"DoT\">", "<Attack>");

                    if (xml.IndexOf("<CalculationOptions>") != xml.LastIndexOf("<CalculationOptions>"))
                    {
                        xml = xml.Substring(0, xml.IndexOf("<CalculationOptions>")) +
                            xml.Substring(xml.LastIndexOf("</CalculationOptions>") + "</CalculationOptions>".Length);
                    }

                    System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(Character));
                    System.IO.StringReader reader = new System.IO.StringReader(xml);
                    character = (Character)serializer.Deserialize(reader);
                    character.IsLoading = false;

                    // decode non-english characters for name and realm
                    if (!String.IsNullOrEmpty(character.Name))
                    {
                        character.Name = Uri.UnescapeDataString(character.Name);
                    }

                    // Realm is now not correctly encoded by rage4 character-loading proxy, but it will work fine in future, I think.
                    if (!String.IsNullOrEmpty(character.Realm))
                    {
                        //character.Realm = Encoding. UnicodeEncoding.Convert(Encoding.UTF8, Encoding.Unicode, character.Realm.ToCharArray());
                        character.Realm = Uri.UnescapeDataString(character.Realm);
                    }

                    character._activeBuffs = new List<Buff>(character._activeBuffsXml.ConvertAll(buff => Buff.GetBuffByName(buff)));
                    character._activeBuffs.RemoveAll(buff => buff == null);
                    // remove all set bonuses, they should no longer be in active buffs
                    character._activeBuffs.RemoveAll(buff => !string.IsNullOrEmpty(buff.SetName));
                    character.itemSetList = new ItemSetList(character._itemSetListXML.ConvertAll(itemset => ItemSet.GenerateItemSetFromSavedString(itemset)));
                    character.itemSetList.RemoveAll(ItemSet => ItemSet == null);
                    character.RecalculateSetBonuses(); // now you can call it
                    foreach (ItemInstance item in character.CustomItemInstances)
                    {
                        item.ForceDisplay = true;
                    }
                    for (int i = 0; i < character.AvailableItems.Count; i++)
                    {
                        // breaking compatibility with 2.0 characters with version 4.0.20
                        // assume all available items at this point are single id green diamonds
                        // or full 7 id gemmed id without random suffix
                        // convert gemmed ids to include random suffix
                        bool dirty = false;
                        string item = character.AvailableItems[i];
                        string[] ids = item.Split('.');
                        if (ids.Length == 7)
                        {
                            string[] nids = new string[] { ids[0], "0", ids[1], ids[2], ids[3], ids[4], ids[5], ids[6] };
                            item = string.Join(".", nids);
                            dirty = true;
                        }
                        if (ids.Length == 5)
                        {
                            string[] nids = new string[] { ids[0], "0", ids[1], ids[2], ids[3], ids[4], "0", "0" };
                            item = string.Join(".", nids);
                            dirty = true;
                        }
                        if (dirty)
                        {
                            character.AvailableItems[i] = item;
                        }
                    }
                    reader.Close();
                }
                catch (Exception ex)
                {
                    new Base.ErrorBox() {
                        Title = "Error Generating Character from XML",
                        Function = "Character.LoadFromXML(...)",
                        TheException = ex,
                    }.Show();
                    character = new Character() { IsLoading = false };
                }
            }
            else
                character = new Character() { IsLoading = false };

            return character;
        }

        public void LoadBuffsFromXml(string path)
        {
            string xml = null;
            if (!string.IsNullOrEmpty(xml))
            {
                try
                {
                    System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(List<string>));
                    System.IO.StringReader reader = new System.IO.StringReader(xml);
                    List<string> buffs = (List<string>)serializer.Deserialize(reader);
                    _activeBuffs = new List<Buff>(buffs.ConvertAll(buff => Buff.GetBuffByName(buff))); ;
                    _activeBuffs.RemoveAll(buff => buff == null);
                    OnCalculationsInvalidated();
                    reader.Close();
                }
                catch (Exception)
                {
                }
            }
        }

        //public string ToCompressedString()
        //{
        //    //TODO: Just messing around with potential ways to serialize a character down to a string short enough to fit in a URL (<2000 characters)

        //    //List<object> objectsToSerialize = new List<object>();
        //    //objectsToSerialize.Add(Name);
        //    //objectsToSerialize.Add(Race);
        //    //objectsToSerialize.Add(Region);
        //    //objectsToSerialize.Add(Realm);
        //    //objectsToSerialize.Add(Class);
        //    //objectsToSerialize.Add(string.Join("|", _item.ConvertAll(itemInstance => itemInstance == null ? string.Empty : itemInstance.GemmedId).ToArray()));
        //    //objectsToSerialize.Add(string.Join("|", _activeBuffs.ConvertAll(buff=>buff.Name).ToArray()));
        //    //objectsToSerialize.Add(CurrentModel);
        //    //objectsToSerialize.Add(CurrentTalents.Data);
        //    //objectsToSerialize.Add(CurrentTalents.GlyphData);
        //    //objectsToSerialize.Add(EnforceGemRequirements);
        //    //objectsToSerialize.Add(WristAugmentSocketEnabled);
        //    //objectsToSerialize.Add(WaistAugmentSocketEnabled);
        //    //objectsToSerialize.Add(HandsAugmentSocketEnabled);
        //    //objectsToSerialize.Add(CalculationOptions.GetXml());
        //    //objectsToSerialize.Add(string.Join("|", AvailableItems.ToArray()));

        //    //MemoryStream stream = new MemoryStream();
        //    //StreamWriter writer = new StreamWriter(stream);
        //    //writer.Write(objectsToSerialize[6].ToString());
        //    //string base64 = System.Convert.ToBase64String(stream.ToArray());


        //    //_serializedCalculationOptions.Clear();
        //    //SerializeCalculationOptions();
        //    //_activeBuffsXml = new List<string>(_activeBuffs.ConvertAll(buff => buff.Name));
        //    //if (this.Class != CharacterClass.DeathKnight) this.DeathKnightTalents = null;
        //    //if (this.Class != CharacterClass.Druid) this.DruidTalents = null;
        //    //if (this.Class != CharacterClass.Hunter) this.HunterTalents = null;
        //    //if (this.Class != CharacterClass.Mage) this.MageTalents = null;
        //    //if (this.Class != CharacterClass.Paladin) this.PaladinTalents = null;
        //    //if (this.Class != CharacterClass.Priest) this.PriestTalents = null;
        //    //if (this.Class != CharacterClass.Assassin) this.RogueTalents = null;
        //    //if (this.Class != CharacterClass.Shaman) this.ShamanTalents = null;
        //    //if (this.Class != CharacterClass.Warlock) this.WarlockTalents = null;
        //    //if (this.Class != CharacterClass.Warrior) this.WarriorTalents = null;

            
        //    ////MemoryStream stream = new MemoryStream();
        //    ////XmlSerializer serializer = new XmlSerializer(typeof(Character));
        //    ////serializer.Serialize(stream, this);
        //    ////StreamReader reader = new StreamReader(stream);
        //    ////string serializedCharacter = reader.ReadToEnd();
        //    ////reader.Close();
        //    ////stream.Close();
        //    ////stream.Dispose();

            

        //    //return "";
        //}

        public static Character FromCompressedString(string characterString)
        {
            return null;
        }
        #endregion
    }

    public interface ICalculationOptionBase { string GetXml(); }
}
